diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml new file mode 100644 index 000000000..c7bd1003a --- /dev/null +++ b/.github/workflows/performance.yml @@ -0,0 +1,34 @@ +name: performance-tests + +on: + push: + branches: + - "main" # or "master" + paths-ignore: + - "**/*.md" + pull_request: + workflow_dispatch: + +env: + TELEMETRY_ENABLED: false + NODE_OPTIONS: "--max_old_space_size=8192" + PR_TITLE: ${{ github.event.pull_request.title }} + APP_HARNESS_HEADLESS: 1 + PYTHONUNBUFFERED: 1 + +jobs: + benchmarks: + name: Run benchmarks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup_build_env + with: + python-version: 3.12.8 + run-poetry-install: true + create-venv-at-path: .venv + - name: Run benchmarks + uses: CodSpeedHQ/action@v3 + with: + token: ${{ secrets.CODSPEED_TOKEN }} + run: poetry run pytest benchmarks/test_evaluate.py --codspeed diff --git a/.gitignore b/.gitignore index 0f7d9e5ff..29a868796 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ assets/external/* dist/* examples/ .web +.states .idea .vscode .coverage @@ -14,3 +15,4 @@ requirements.txt .pyi_generator_last_run .pyi_generator_diff reflex.db +.codspeed \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 54d8f3d72..dbe069ae8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,7 +28,7 @@ repos: entry: python3 scripts/make_pyi.py - repo: https://github.com/RobertCraigie/pyright-python - rev: v1.1.334 + rev: v1.1.392 hooks: - id: pyright args: [reflex, tests] diff --git a/benchmarks/test_benchmark_compile_components.py b/benchmarks/test_benchmark_compile_components.py index ba7e9c571..9bcfbf85b 100644 --- a/benchmarks/test_benchmark_compile_components.py +++ b/benchmarks/test_benchmark_compile_components.py @@ -34,13 +34,13 @@ def render_component(num: int): rx.box( rx.accordion.root( rx.accordion.item( - header="Full Ingredients", # type: ignore - content="Yes. It's built with accessibility in mind.", # type: ignore + header="Full Ingredients", + content="Yes. It's built with accessibility in mind.", font_size="3em", ), rx.accordion.item( - header="Applications", # type: ignore - content="Yes. It's unstyled by default, giving you freedom over the look and feel.", # type: ignore + header="Applications", + content="Yes. It's unstyled by default, giving you freedom over the look and feel.", ), collapsible=True, variant="ghost", @@ -166,9 +166,9 @@ def app_with_10_components( root=root, app_source=functools.partial( AppWithTenComponentsOnePage, - render_component=render_component, # type: ignore + render_component=render_component, # pyright: ignore [reportCallIssue] ), - ) # type: ignore + ) @pytest.fixture(scope="session") @@ -189,9 +189,9 @@ def app_with_100_components( root=root, app_source=functools.partial( AppWithHundredComponentOnePage, - render_component=render_component, # type: ignore + render_component=render_component, # pyright: ignore [reportCallIssue] ), - ) # type: ignore + ) @pytest.fixture(scope="session") @@ -212,9 +212,9 @@ def app_with_1000_components( root=root, app_source=functools.partial( AppWithThousandComponentsOnePage, - render_component=render_component, # type: ignore + render_component=render_component, # pyright: ignore [reportCallIssue] ), - ) # type: ignore + ) @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON) diff --git a/benchmarks/test_benchmark_compile_pages.py b/benchmarks/test_benchmark_compile_pages.py index a5c85810e..149fc6130 100644 --- a/benchmarks/test_benchmark_compile_pages.py +++ b/benchmarks/test_benchmark_compile_pages.py @@ -28,7 +28,7 @@ def render_multiple_pages(app, num: int): """ from typing import Tuple - from rxconfig import config # type: ignore + from rxconfig import config # pyright: ignore [reportMissingImports] import reflex as rx @@ -74,13 +74,13 @@ def render_multiple_pages(app, num: int): rx.select( ["C", "PF", "SF", "PG", "SG"], placeholder="Select a position. (All)", - on_change=State.set_position, # type: ignore + on_change=State.set_position, # pyright: ignore [reportAttributeAccessIssue] size="3", ), rx.select( college, placeholder="Select a college. (All)", - on_change=State.set_college, # type: ignore + on_change=State.set_college, # pyright: ignore [reportAttributeAccessIssue] size="3", ), ), @@ -95,7 +95,7 @@ def render_multiple_pages(app, num: int): default_value=[18, 50], min=18, max=50, - on_value_commit=State.set_age, # type: ignore + on_value_commit=State.set_age, # pyright: ignore [reportAttributeAccessIssue] ), align_items="left", width="100%", @@ -110,7 +110,7 @@ def render_multiple_pages(app, num: int): default_value=[0, 25000000], min=0, max=25000000, - on_value_commit=State.set_salary, # type: ignore + on_value_commit=State.set_salary, # pyright: ignore [reportAttributeAccessIssue] ), align_items="left", width="100%", @@ -130,7 +130,7 @@ def render_multiple_pages(app, num: int): def AppWithOnePage(): """A reflex app with one page.""" - from rxconfig import config # type: ignore + from rxconfig import config # pyright: ignore [reportMissingImports] import reflex as rx @@ -232,7 +232,7 @@ def app_with_ten_pages( root=root, app_source=functools.partial( AppWithTenPages, - render_comp=render_multiple_pages, # type: ignore + render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue] ), ) @@ -255,9 +255,9 @@ def app_with_hundred_pages( root=root, app_source=functools.partial( AppWithHundredPages, - render_comp=render_multiple_pages, # type: ignore + render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue] ), - ) # type: ignore + ) @pytest.fixture(scope="session") @@ -278,9 +278,9 @@ def app_with_thousand_pages( root=root, app_source=functools.partial( AppWithThousandPages, - render_comp=render_multiple_pages, # type: ignore + render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue] ), - ) # type: ignore + ) @pytest.fixture(scope="session") @@ -301,9 +301,9 @@ def app_with_ten_thousand_pages( root=root, app_source=functools.partial( AppWithTenThousandPages, - render_comp=render_multiple_pages, # type: ignore + render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue] ), - ) # type: ignore + ) @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON) diff --git a/benchmarks/test_evaluate.py b/benchmarks/test_evaluate.py new file mode 100644 index 000000000..aa4c8237e --- /dev/null +++ b/benchmarks/test_evaluate.py @@ -0,0 +1,231 @@ +from dataclasses import dataclass +from typing import cast + +import pytest + +import reflex as rx + + +class SideBarState(rx.State): + """State for the side bar.""" + + current_page: rx.Field[str] = rx.field("/") + + +@dataclass(frozen=True) +class SideBarPage: + """A page in the side bar.""" + + title: str + href: str + + +@dataclass(frozen=True) +class SideBarSection: + """A section in the side bar.""" + + name: str + icon: str + pages: tuple[SideBarPage, ...] + + +@dataclass(frozen=True) +class Category: + """A category in the side bar.""" + + name: str + href: str + sections: tuple[SideBarSection, ...] + + +SIDE_BAR = ( + Category( + name="General", + href="/", + sections=( + SideBarSection( + name="Home", + icon="home", + pages=( + SideBarPage(title="Home", href="/"), + SideBarPage(title="Contact", href="/contact"), + ), + ), + SideBarSection( + name="About", + icon="info", + pages=( + SideBarPage(title="About", href="/about"), + SideBarPage(title="FAQ", href="/faq"), + ), + ), + ), + ), + Category( + name="Projects", + href="/projects", + sections=( + SideBarSection( + name="Python", + icon="worm", + pages=( + SideBarPage(title="Python", href="/projects/python"), + SideBarPage(title="Django", href="/projects/django"), + SideBarPage(title="Flask", href="/projects/flask"), + SideBarPage(title="FastAPI", href="/projects/fastapi"), + SideBarPage(title="Pyramid", href="/projects/pyramid"), + SideBarPage(title="Tornado", href="/projects/tornado"), + SideBarPage(title="TurboGears", href="/projects/turbogears"), + SideBarPage(title="Web2py", href="/projects/web2py"), + SideBarPage(title="Zope", href="/projects/zope"), + SideBarPage(title="Plone", href="/projects/plone"), + SideBarPage(title="Quixote", href="/projects/quixote"), + SideBarPage(title="Bottle", href="/projects/bottle"), + SideBarPage(title="CherryPy", href="/projects/cherrypy"), + SideBarPage(title="Falcon", href="/projects/falcon"), + SideBarPage(title="Sanic", href="/projects/sanic"), + SideBarPage(title="Starlette", href="/projects/starlette"), + ), + ), + SideBarSection( + name="JavaScript", + icon="banana", + pages=( + SideBarPage(title="JavaScript", href="/projects/javascript"), + SideBarPage(title="Angular", href="/projects/angular"), + SideBarPage(title="React", href="/projects/react"), + SideBarPage(title="Vue", href="/projects/vue"), + SideBarPage(title="Ember", href="/projects/ember"), + SideBarPage(title="Backbone", href="/projects/backbone"), + SideBarPage(title="Meteor", href="/projects/meteor"), + SideBarPage(title="Svelte", href="/projects/svelte"), + SideBarPage(title="Preact", href="/projects/preact"), + SideBarPage(title="Mithril", href="/projects/mithril"), + SideBarPage(title="Aurelia", href="/projects/aurelia"), + SideBarPage(title="Polymer", href="/projects/polymer"), + SideBarPage(title="Knockout", href="/projects/knockout"), + SideBarPage(title="Dojo", href="/projects/dojo"), + SideBarPage(title="Riot", href="/projects/riot"), + SideBarPage(title="Alpine", href="/projects/alpine"), + SideBarPage(title="Stimulus", href="/projects/stimulus"), + SideBarPage(title="Marko", href="/projects/marko"), + SideBarPage(title="Sapper", href="/projects/sapper"), + SideBarPage(title="Nuxt", href="/projects/nuxt"), + SideBarPage(title="Next", href="/projects/next"), + SideBarPage(title="Gatsby", href="/projects/gatsby"), + SideBarPage(title="Gridsome", href="/projects/gridsome"), + SideBarPage(title="Nest", href="/projects/nest"), + SideBarPage(title="Express", href="/projects/express"), + SideBarPage(title="Koa", href="/projects/koa"), + SideBarPage(title="Hapi", href="/projects/hapi"), + SideBarPage(title="LoopBack", href="/projects/loopback"), + SideBarPage(title="Feathers", href="/projects/feathers"), + SideBarPage(title="Sails", href="/projects/sails"), + SideBarPage(title="Adonis", href="/projects/adonis"), + SideBarPage(title="Meteor", href="/projects/meteor"), + SideBarPage(title="Derby", href="/projects/derby"), + SideBarPage(title="Socket.IO", href="/projects/socketio"), + ), + ), + ), + ), +) + + +def side_bar_page(page: SideBarPage): + return rx.box( + rx.link( + page.title, + href=page.href, + ) + ) + + +def side_bar_section(section: SideBarSection): + return rx.accordion.item( + rx.accordion.header( + rx.accordion.trigger( + rx.hstack( + rx.hstack( + rx.icon(section.icon), + section.name, + align="center", + ), + rx.accordion.icon(), + width="100%", + justify="between", + ) + ) + ), + rx.accordion.content( + rx.vstack( + *map(side_bar_page, section.pages), + ), + border_inline_start="1px solid", + padding_inline_start="1em", + margin_inline_start="1.5em", + ), + value=section.name, + width="100%", + variant="ghost", + ) + + +def side_bar_category(category: Category): + selected_section = cast( + rx.Var, + rx.match( + SideBarState.current_page, + *[ + ( + section.name, + section.name, + ) + for section in category.sections + ], + None, + ), + ) + return rx.vstack( + rx.heading( + rx.link( + category.name, + href=category.href, + ), + size="5", + ), + rx.accordion.root( + *map(side_bar_section, category.sections), + default_value=selected_section.to(str), + variant="ghost", + width="100%", + collapsible=True, + type="multiple", + ), + width="100%", + ) + + +def side_bar(): + return rx.vstack( + *map(side_bar_category, SIDE_BAR), + width="fit-content", + ) + + +LOREM_IPSUM = "Lorem ipsum dolor sit amet, dolor ut dolore pariatur aliqua enim tempor sed. Labore excepteur sed exercitation. Ullamco aliquip lorem sunt enim in incididunt. Magna anim officia sint cillum labore. Ut eu non dolore minim nostrud magna eu, aute ex in incididunt irure eu. Fugiat et magna magna est excepteur eiusmod minim. Quis eiusmod et non pariatur dolor veniam incididunt, eiusmod irure enim sed dolor lorem pariatur do. Occaecat duis irure excepteur dolore. Proident ut laborum pariatur sit sit, nisi nostrud voluptate magna commodo laborum esse velit. Voluptate non minim deserunt adipiscing irure deserunt cupidatat. Laboris veniam commodo incididunt veniam lorem occaecat, fugiat ipsum dolor cupidatat. Ea officia sed eu excepteur culpa adipiscing, tempor consectetur ullamco eu. Anim ex proident nulla sunt culpa, voluptate veniam proident est adipiscing sint elit velit. Laboris adipiscing est culpa cillum magna. Sit veniam nulla nulla, aliqua eiusmod commodo lorem cupidatat commodo occaecat. Fugiat cillum dolor incididunt mollit eiusmod sint. Non lorem dolore labore excepteur minim laborum sed. Irure nisi do lorem nulla sunt commodo, deserunt quis mollit consectetur minim et esse est, proident nostrud officia enim sed reprehenderit. Magna cillum consequat aute reprehenderit duis sunt ullamco. Labore qui mollit voluptate. Duis dolor sint aute amet aliquip officia, est non mollit tempor enim quis fugiat, eu do culpa consectetur magna. Do ullamco aliqua voluptate culpa excepteur reprehenderit reprehenderit. Occaecat nulla sit est magna. Deserunt ea voluptate veniam cillum. Amet cupidatat duis est tempor fugiat ex eu, officia est sunt consectetur labore esse exercitation. Nisi cupidatat irure est nisi. Officia amet eu veniam reprehenderit. In amet incididunt tempor commodo ea labore. Mollit dolor aliquip excepteur, voluptate aute occaecat id officia proident. Ullamco est amet tempor. Proident aliquip proident mollit do aliquip ipsum, culpa quis aute id irure. Velit excepteur cillum cillum ut cupidatat. Occaecat qui elit esse nulla minim. Consequat velit id ad pariatur tempor. Eiusmod deserunt aliqua ex sed quis non. Dolor sint commodo ex in deserunt nostrud excepteur, pariatur ex aliqua anim adipiscing amet proident. Laboris eu laborum magna lorem ipsum fugiat velit." + + +def complicated_page(): + return rx.hstack( + side_bar(), + rx.box( + rx.heading("Complicated Page", size="1"), + rx.text(LOREM_IPSUM), + ), + ) + + +@pytest.mark.benchmark +def test_component_init(): + complicated_page() diff --git a/poetry.lock b/poetry.lock index f492584d0..0b4f489dc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "alembic" @@ -6,6 +6,8 @@ version = "1.14.1" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "alembic-1.14.1-py3-none-any.whl", hash = "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5"}, {file = "alembic-1.14.1.tar.gz", hash = "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213"}, @@ -25,6 +27,8 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -36,6 +40,8 @@ version = "4.8.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, @@ -58,6 +64,8 @@ version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_full_version < \"3.11.3\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -69,6 +77,8 @@ version = "0.13.0" description = "Enhance the standard unittest package with features for testing asyncio libraries" optional = false python-versions = ">=3.5" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, @@ -76,13 +86,15 @@ files = [ [[package]] name = "attrs" -version = "24.3.0" +version = "25.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, - {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, + {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, + {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, ] [package.extras] @@ -99,6 +111,8 @@ version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version <= \"3.11\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, @@ -114,6 +128,8 @@ version = "0.23.1" description = "The bidirectional mapping library for Python." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"}, {file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"}, @@ -125,6 +141,8 @@ version = "1.2.2.post1" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"}, {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"}, @@ -150,6 +168,8 @@ version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, @@ -161,6 +181,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -230,6 +251,7 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] +markers = {main = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\" and (python_version <= \"3.11\" or python_version >= \"3.12\")", dev = "python_version <= \"3.11\" or python_version >= \"3.12\""} [package.dependencies] pycparser = "*" @@ -240,6 +262,8 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -251,6 +275,8 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -352,6 +378,8 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -366,10 +394,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "(platform_system == \"Windows\" or os_name == \"nt\") and (python_version <= \"3.11\" or python_version >= \"3.12\")", dev = "(python_version <= \"3.11\" or python_version >= \"3.12\") and sys_platform == \"win32\""} [[package]] name = "coverage" @@ -377,6 +407,8 @@ version = "7.6.10" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, @@ -454,6 +486,8 @@ version = "44.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and (python_version <= \"3.11\" or python_version >= \"3.12\")" files = [ {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, @@ -505,6 +539,8 @@ version = "1.8.1" description = "A utility for ensuring Google-style docstrings stay up to date with the source code." optional = false python-versions = ">=3.6,<4.0" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, @@ -516,6 +552,8 @@ version = "0.3.9" description = "serialize all of Python" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, @@ -531,6 +569,8 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -542,6 +582,8 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main"] +markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and sys_platform == \"linux\"" files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -553,6 +595,8 @@ version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, @@ -564,6 +608,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -574,23 +620,25 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.115.6" +version = "0.115.7" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, - {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, + {file = "fastapi-0.115.7-py3-none-any.whl", hash = "sha256:eb6a8c8bf7f26009e8147111ff15b5177a0e19bb4a45bc3486ab14804539d21e"}, + {file = "fastapi-0.115.7.tar.gz", hash = "sha256:0f106da6c01d88a6786b3248fb4d7a940d071f6f488488898ad5d354b25ed015"}, ] [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.40.0,<0.42.0" +starlette = ">=0.40.0,<0.46.0" typing-extensions = ">=4.8.0" [package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "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)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "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)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "filelock" @@ -598,6 +646,8 @@ version = "3.17.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, @@ -614,6 +664,7 @@ version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, @@ -689,6 +740,7 @@ files = [ {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, ] +markers = {main = "(python_version <= \"3.11\" or python_version >= \"3.12\") and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and python_version < \"3.14\"", dev = "python_version <= \"3.11\" or python_version >= \"3.12\""} [package.extras] docs = ["Sphinx", "furo"] @@ -700,6 +752,8 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -721,6 +775,8 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -732,6 +788,8 @@ version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, @@ -753,6 +811,8 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -777,6 +837,8 @@ version = "1.5.0" description = "A tool for generating OIDC identities" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "id-1.5.0-py3-none-any.whl", hash = "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658"}, {file = "id-1.5.0.tar.gz", hash = "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d"}, @@ -796,6 +858,8 @@ version = "2.6.6" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881"}, {file = "identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251"}, @@ -810,6 +874,8 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -824,6 +890,8 @@ version = "8.6.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version <= \"3.11\" or python_full_version < \"3.10.2\"" files = [ {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, @@ -847,6 +915,8 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -858,6 +928,8 @@ version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, @@ -876,6 +948,8 @@ version = "6.0.1" description = "Useful decorators and context managers" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" files = [ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, @@ -894,6 +968,8 @@ version = "4.1.0" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" files = [ {file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"}, {file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"}, @@ -916,6 +992,8 @@ version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and (python_version <= \"3.11\" or python_version >= \"3.12\")" files = [ {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, @@ -931,6 +1009,8 @@ version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, @@ -948,6 +1028,8 @@ version = "25.6.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" files = [ {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, @@ -977,6 +1059,8 @@ version = "0.4" description = "Makes it easy to load subpackages and functions on demand." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc"}, {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"}, @@ -996,6 +1080,8 @@ version = "1.3.8" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"}, {file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"}, @@ -1015,6 +1101,8 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -1039,6 +1127,8 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1109,6 +1199,8 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -1120,6 +1212,8 @@ version = "10.6.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "(python_version <= \"3.11\" or python_version >= \"3.12\") and (platform_machine != \"ppc64le\" and platform_machine != \"s390x\")" files = [ {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"}, {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"}, @@ -1131,6 +1225,8 @@ version = "0.2.20" description = "Python binding to Ammonia HTML sanitizer Rust crate" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "nh3-0.2.20-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e1061a4ab6681f6bdf72b110eea0c4e1379d57c9de937db3be4202f7ad6043db"}, {file = "nh3-0.2.20-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb4254b1dac4a1ee49919a5b3f1caf9803ea8dada1816d9e8289e63d3cd0dd9a"}, @@ -1164,6 +1260,8 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1175,6 +1273,8 @@ version = "2.2.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"}, {file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"}, @@ -1239,6 +1339,8 @@ version = "1.3.0.post0" description = "Capture the outcome of Python function calls." optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, @@ -1253,6 +1355,8 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1264,6 +1368,8 @@ version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, @@ -1350,6 +1456,8 @@ version = "11.1.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"}, {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"}, @@ -1432,41 +1540,14 @@ tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "ole typing = ["typing-extensions"] xmp = ["defusedxml"] -[[package]] -name = "pip" -version = "24.3.1" -description = "The PyPA recommended tool for installing Python packages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pip-24.3.1-py3-none-any.whl", hash = "sha256:3790624780082365f47549d032f3770eeb2b1e8bd1f7b2e02dace1afa361b4ed"}, - {file = "pip-24.3.1.tar.gz", hash = "sha256:ebcb60557f2aefabc2e0f918751cd24ea0d56d8ec5445fe1807f1d2109660b99"}, -] - -[[package]] -name = "pipdeptree" -version = "2.16.2" -description = "Command line utility to show dependency tree of packages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pipdeptree-2.16.2-py3-none-any.whl", hash = "sha256:4b60a20f632aa3449880141d1cd0bc99cb5f93ed46d54d689fd1c9b95f0e53d0"}, - {file = "pipdeptree-2.16.2.tar.gz", hash = "sha256:96ecde8e6f40c95998491a385e4af56d387f94ff7d3b8f209aa34982a721bc43"}, -] - -[package.dependencies] -pip = ">=23.1.2" - -[package.extras] -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 = "platformdirs" version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -1483,6 +1564,8 @@ version = "1.49.1" description = "A high-level API to automate web browsers" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "playwright-1.49.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:1041ffb45a0d0bc44d698d3a5aa3ac4b67c9bd03540da43a0b70616ad52592b8"}, {file = "playwright-1.49.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9f38ed3d0c1f4e0a6d1c92e73dd9a61f8855133249d6f0cec28648d38a7137be"}, @@ -1503,6 +1586,8 @@ version = "5.24.1" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, @@ -1518,6 +1603,8 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1533,6 +1620,8 @@ version = "4.1.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"}, {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"}, @@ -1551,6 +1640,8 @@ version = "6.1.1" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, @@ -1581,6 +1672,8 @@ version = "9.0.0" description = "Get CPU info with pure Python" optional = false python-versions = "*" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, @@ -1592,20 +1685,24 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +markers = {main = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\" and (python_version <= \"3.11\" or python_version >= \"3.12\")", dev = "python_version <= \"3.11\" or python_version >= \"3.12\""} [[package]] name = "pydantic" -version = "2.10.5" +version = "2.10.6" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, - {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, + {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, + {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, ] [package.dependencies] @@ -1623,6 +1720,8 @@ version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -1735,6 +1834,8 @@ version = "12.0.0" description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyee-12.0.0-py3-none-any.whl", hash = "sha256:7b14b74320600049ccc7d0e0b1becd3b4bd0a03c745758225e31a59f4095c990"}, {file = "pyee-12.0.0.tar.gz", hash = "sha256:c480603f4aa2927d4766eb41fa82793fe60a82cbfdb8d688e0d08c55a534e145"}, @@ -1752,6 +1853,8 @@ version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -1766,6 +1869,8 @@ version = "1.2.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, @@ -1777,6 +1882,8 @@ version = "1.1.392.post0" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyright-1.1.392.post0-py3-none-any.whl", hash = "sha256:252f84458a46fa2f0fd4e2f91fc74f50b9ca52c757062e93f6c250c0d8329eb2"}, {file = "pyright-1.1.392.post0.tar.gz", hash = "sha256:3b7f88de74a28dcfa90c7d90c782b6569a48c2be5f9d4add38472bdaac247ebd"}, @@ -1797,6 +1904,8 @@ version = "1.7.1" description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, @@ -1809,6 +1918,8 @@ version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -1827,13 +1938,15 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-asyncio" -version = "0.25.2" +version = "0.25.3" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "pytest_asyncio-0.25.2-py3-none-any.whl", hash = "sha256:0d0bb693f7b99da304a0634afc0a4b19e49d5e0de2d670f38dc4bfa5727c5075"}, - {file = "pytest_asyncio-0.25.2.tar.gz", hash = "sha256:3f8ef9a98f45948ea91a0ed3dc4268b5326c0e7bce73892acc654df4262ad45f"}, + {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"}, + {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"}, ] [package.dependencies] @@ -1849,6 +1962,8 @@ version = "2.1.0" description = "pytest plugin for URL based testing" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6"}, {file = "pytest_base_url-2.1.0.tar.gz", hash = "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45"}, @@ -1867,6 +1982,8 @@ version = "5.1.0" description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-benchmark-5.1.0.tar.gz", hash = "sha256:9ea661cdc292e8231f7cd4c10b0319e56a2118e2c09d9f50e1b3d150d2aca105"}, {file = "pytest_benchmark-5.1.0-py3-none-any.whl", hash = "sha256:922de2dfa3033c227c96da942d1878191afa135a29485fb942e85dff1c592c89"}, @@ -1881,12 +1998,47 @@ aspect = ["aspectlib"] elasticsearch = ["elasticsearch"] histogram = ["pygal", "pygaljs", "setuptools"] +[[package]] +name = "pytest-codspeed" +version = "3.1.2" +description = "Pytest plugin to create CodSpeed benchmarks" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pytest_codspeed-3.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aed496f873670ce0ea8f980a7c1a2c6a08f415e0ebdf207bf651b2d922103374"}, + {file = "pytest_codspeed-3.1.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee45b0b763f6b5fa5d74c7b91d694a9615561c428b320383660672f4471756e3"}, + {file = "pytest_codspeed-3.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c84e591a7a0f67d45e2dc9fd05b276971a3aabcab7478fe43363ebefec1358f4"}, + {file = "pytest_codspeed-3.1.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6ae6d094247156407770e6b517af70b98862dd59a3c31034aede11d5f71c32c"}, + {file = "pytest_codspeed-3.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0f264991de5b5cdc118b96fc671386cca3f0f34e411482939bf2459dc599097"}, + {file = "pytest_codspeed-3.1.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0695a4bcd5ff04e8379124dba5d9795ea5e0cadf38be7a0406432fc1467b555"}, + {file = "pytest_codspeed-3.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc356c8dcaaa883af83310f397ac06c96fac9b8a1146e303d4b374b2cb46a18"}, + {file = "pytest_codspeed-3.1.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc8a5d0366322a75cf562f7d8d672d28c1cf6948695c4dddca50331e08f6b3d5"}, + {file = "pytest_codspeed-3.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c5fe7a19b72f54f217480b3b527102579547b1de9fe3acd9e66cb4629ff46c8"}, + {file = "pytest_codspeed-3.1.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b67205755a665593f6521a98317d02a9d07d6fdc593f6634de2c94dea47a3055"}, + {file = "pytest_codspeed-3.1.2-py3-none-any.whl", hash = "sha256:5e7ed0315e33496c5c07dba262b50303b8d0bc4c3d10bf1d422a41e70783f1cb"}, + {file = "pytest_codspeed-3.1.2.tar.gz", hash = "sha256:09c1733af3aab35e94a621aa510f2d2114f65591e6f644c42ca3f67547edad4b"}, +] + +[package.dependencies] +cffi = ">=1.17.1" +pytest = ">=3.8" +rich = ">=13.8.1" + +[package.extras] +compat = ["pytest-benchmark (>=5.0.0,<5.1.0)", "pytest-xdist (>=3.6.1,<3.7.0)"] +lint = ["mypy (>=1.11.2,<1.12.0)", "ruff (>=0.6.5,<0.7.0)"] +test = ["pytest (>=7.0,<8.0)", "pytest-cov (>=4.0.0,<4.1.0)"] + [[package]] name = "pytest-cov" version = "6.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, @@ -1905,6 +2057,8 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -1922,6 +2076,8 @@ version = "0.6.2" description = "A pytest wrapper with fixtures for Playwright to automate web browsers" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest_playwright-0.6.2-py3-none-any.whl", hash = "sha256:0eff73bebe497b0158befed91e2f5fe94cfa17181f8b3acf575beed84e7e9043"}, {file = "pytest_playwright-0.6.2.tar.gz", hash = "sha256:ff4054b19aa05df096ac6f74f0572591566aaf0f6d97f6cb9674db8a4d4ed06c"}, @@ -1939,6 +2095,8 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1953,6 +2111,8 @@ version = "4.11.2" description = "Engine.IO server and client for Python" optional = false python-versions = ">=3.6" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python_engineio-4.11.2-py3-none-any.whl", hash = "sha256:f0971ac4c65accc489154fe12efd88f53ca8caf04754c46a66e85f5102ef22ad"}, {file = "python_engineio-4.11.2.tar.gz", hash = "sha256:145bb0daceb904b4bb2d3eb2d93f7dbb7bb87a6a0c4f20a94cc8654dec977129"}, @@ -1972,6 +2132,8 @@ version = "0.0.20" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, @@ -1983,6 +2145,8 @@ version = "8.0.4" description = "A Python slugify application that also handles Unicode" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, @@ -2000,6 +2164,8 @@ version = "5.12.1" description = "Socket.IO server and client for Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python_socketio-5.12.1-py3-none-any.whl", hash = "sha256:24a0ea7cfff0e021eb28c68edbf7914ee4111bdf030b95e4d250c4dc9af7a386"}, {file = "python_socketio-5.12.1.tar.gz", hash = "sha256:0299ff1f470b676c09c1bfab1dead25405077d227b2c13cf217a34dadc68ba9c"}, @@ -2020,6 +2186,8 @@ version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, @@ -2031,6 +2199,8 @@ version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"win32\" and (python_version <= \"3.11\" or python_version >= \"3.12\")" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, @@ -2042,6 +2212,8 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -2104,6 +2276,8 @@ version = "44.0" description = "readme_renderer is a library for rendering readme descriptions for Warehouse" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151"}, {file = "readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1"}, @@ -2123,6 +2297,8 @@ version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, @@ -2137,27 +2313,26 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)" [[package]] name = "reflex-hosting-cli" -version = "0.1.32" +version = "0.1.33" description = "Reflex Hosting CLI" optional = false -python-versions = "<4.0,>=3.8" +python-versions = "<4.0,>=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "reflex_hosting_cli-0.1.32-py3-none-any.whl", hash = "sha256:86b4222f3e99d949a209be7de8c457ededebc1f12a721ee6669c6c35fdecc508"}, - {file = "reflex_hosting_cli-0.1.32.tar.gz", hash = "sha256:0b8e4b4b30d9261bf6d720265f1c428b2840bb630896e60a1a2faa095901ed59"}, + {file = "reflex_hosting_cli-0.1.33-py3-none-any.whl", hash = "sha256:3fe72fc448a231c61de4ac646f42c936c70e91330f616a23aec658f905d53bc4"}, + {file = "reflex_hosting_cli-0.1.33.tar.gz", hash = "sha256:81c4a896b106eea99f1cab53ea23a6e19802592ce0468cc38d93d440bc95263a"}, ] [package.dependencies] charset-normalizer = ">=3.3.2,<4.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" pyyaml = ">=6.0.2,<7.0.0" rich = ">=13.0.0,<14.0" tabulate = ">=0.9.0,<0.10.0" typer = ">=0.15.0,<1" -websockets = ">=10.4" [[package]] name = "requests" @@ -2165,6 +2340,8 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2186,6 +2363,8 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -2200,6 +2379,8 @@ version = "2.0.0" description = "Validating URI References per RFC 3986" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, @@ -2214,6 +2395,8 @@ version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, @@ -2233,6 +2416,8 @@ version = "0.8.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "ruff-0.8.2-py3-none-linux_armv6l.whl", hash = "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d"}, {file = "ruff-0.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5"}, @@ -2260,6 +2445,8 @@ version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.6" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and (python_version <= \"3.11\" or python_version >= \"3.12\")" files = [ {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, @@ -2271,13 +2458,15 @@ jeepney = ">=0.6" [[package]] name = "selenium" -version = "4.28.0" +version = "4.28.1" description = "Official Python bindings for Selenium WebDriver" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "selenium-4.28.0-py3-none-any.whl", hash = "sha256:3d6a2e8e1b850a1078884ea19f4e011ecdc12263434d87a0b78769836fb82dd8"}, - {file = "selenium-4.28.0.tar.gz", hash = "sha256:a9fae6eef48d470a1b0c6e45185d96f0dafb025e8da4b346cc41e4da3ac54fa0"}, + {file = "selenium-4.28.1-py3-none-any.whl", hash = "sha256:4238847e45e24e4472cfcf3554427512c7aab9443396435b1623ef406fff1cc1"}, + {file = "selenium-4.28.1.tar.gz", hash = "sha256:0072d08670d7ec32db901bd0107695a330cecac9f196e3afb3fa8163026e022a"}, ] [package.dependencies] @@ -2294,6 +2483,8 @@ version = "75.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, @@ -2314,6 +2505,8 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -2325,6 +2518,8 @@ version = "1.1.0" description = "Simple WebSocket server and client for Python" optional = false python-versions = ">=3.6" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c"}, {file = "simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4"}, @@ -2343,6 +2538,8 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2354,6 +2551,8 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2365,6 +2564,8 @@ version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" optional = false python-versions = "*" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, @@ -2376,6 +2577,8 @@ version = "2.0.37" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e"}, {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069"}, @@ -2457,6 +2660,8 @@ version = "0.0.22" description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "sqlmodel-0.0.22-py3-none-any.whl", hash = "sha256:a1ed13e28a1f4057cbf4ff6cdb4fc09e85702621d3259ba17b3c230bfb2f941b"}, {file = "sqlmodel-0.0.22.tar.gz", hash = "sha256:7d37c882a30c43464d143e35e9ecaf945d88035e20117bf5ec2834a23cbe505e"}, @@ -2468,20 +2673,22 @@ SQLAlchemy = ">=2.0.14,<2.1.0" [[package]] name = "starlette" -version = "0.41.3" +version = "0.45.3" description = "The little ASGI library that shines." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ - {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, - {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, + {file = "starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d"}, + {file = "starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f"}, ] [package.dependencies] -anyio = ">=3.4.0,<5" +anyio = ">=3.6.2,<5" [package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] [[package]] name = "starlette-admin" @@ -2489,6 +2696,8 @@ version = "0.14.1" description = "Fast, beautiful and extensible administrative interface framework for Starlette/FastApi applications" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "starlette_admin-0.14.1-py3-none-any.whl", hash = "sha256:5b6260d7ed3db455585852d669feb7ed9a8c5f9a1e3d48d21a52912ec37e18f9"}, {file = "starlette_admin-0.14.1.tar.gz", hash = "sha256:45e2baa3b9a8deec7a6e8ca9295123f648bb0d2070abe68f27193c6d5e32cc38"}, @@ -2512,6 +2721,8 @@ version = "0.9.0" description = "Pretty-print tabular data" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -2526,6 +2737,8 @@ version = "9.0.0" description = "Retry code until it succeeds" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, @@ -2541,6 +2754,8 @@ version = "1.3" description = "The most basic Text::Unidecode port" optional = false python-versions = "*" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, @@ -2552,6 +2767,8 @@ version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -2563,6 +2780,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -2604,6 +2823,8 @@ version = "0.13.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, @@ -2615,6 +2836,8 @@ version = "0.28.0" description = "A friendly Python library for async concurrency and I/O" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "trio-0.28.0-py3-none-any.whl", hash = "sha256:56d58977acc1635735a96581ec70513cc781b8b6decd299c487d3be2a721cd94"}, {file = "trio-0.28.0.tar.gz", hash = "sha256:4e547896fe9e8a5658e54e4c7c5fa1db748cbbbaa7c965e7d40505b928c73c05"}, @@ -2635,6 +2858,8 @@ version = "0.11.1" description = "WebSocket library for Trio" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"}, {file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"}, @@ -2651,6 +2876,8 @@ version = "6.1.0" description = "Collection of utilities for publishing packages on PyPI" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "twine-6.1.0-py3-none-any.whl", hash = "sha256:a47f973caf122930bf0fbbf17f80b83bc1602c9ce393c7845f289a3001dc5384"}, {file = "twine-6.1.0.tar.gz", hash = "sha256:be324f6272eff91d07ee93f251edf232fc647935dd585ac003539b42404a8dbd"}, @@ -2676,6 +2903,8 @@ version = "0.15.1" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847"}, {file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a"}, @@ -2693,6 +2922,8 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -2704,6 +2935,8 @@ version = "2025.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, @@ -2715,6 +2948,8 @@ version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, @@ -2735,6 +2970,8 @@ version = "0.34.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, @@ -2754,6 +2991,8 @@ version = "20.29.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779"}, {file = "virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35"}, @@ -2774,6 +3013,8 @@ version = "1.8.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, @@ -2784,90 +3025,14 @@ docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] -[[package]] -name = "websockets" -version = "14.2" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.9" -files = [ - {file = "websockets-14.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8179f95323b9ab1c11723e5d91a89403903f7b001828161b480a7810b334885"}, - {file = "websockets-14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d8c3e2cdb38f31d8bd7d9d28908005f6fa9def3324edb9bf336d7e4266fd397"}, - {file = "websockets-14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:714a9b682deb4339d39ffa674f7b674230227d981a37d5d174a4a83e3978a610"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e53c72052f2596fb792a7acd9704cbc549bf70fcde8a99e899311455974ca3"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3fbd68850c837e57373d95c8fe352203a512b6e49eaae4c2f4088ef8cf21980"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b27ece32f63150c268593d5fdb82819584831a83a3f5809b7521df0685cd5d8"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4daa0faea5424d8713142b33825fff03c736f781690d90652d2c8b053345b0e7"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bc63cee8596a6ec84d9753fd0fcfa0452ee12f317afe4beae6b157f0070c6c7f"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a570862c325af2111343cc9b0257b7119b904823c675b22d4ac547163088d0d"}, - {file = "websockets-14.2-cp310-cp310-win32.whl", hash = "sha256:75862126b3d2d505e895893e3deac0a9339ce750bd27b4ba515f008b5acf832d"}, - {file = "websockets-14.2-cp310-cp310-win_amd64.whl", hash = "sha256:cc45afb9c9b2dc0852d5c8b5321759cf825f82a31bfaf506b65bf4668c96f8b2"}, - {file = "websockets-14.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bdc8c692c866ce5fefcaf07d2b55c91d6922ac397e031ef9b774e5b9ea42166"}, - {file = "websockets-14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c93215fac5dadc63e51bcc6dceca72e72267c11def401d6668622b47675b097f"}, - {file = "websockets-14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c9b6535c0e2cf8a6bf938064fb754aaceb1e6a4a51a80d884cd5db569886910"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a52a6d7cf6938e04e9dceb949d35fbdf58ac14deea26e685ab6368e73744e4c"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f05702e93203a6ff5226e21d9b40c037761b2cfb637187c9802c10f58e40473"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22441c81a6748a53bfcb98951d58d1af0661ab47a536af08920d129b4d1c3473"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd9b868d78b194790e6236d9cbc46d68aba4b75b22497eb4ab64fa640c3af56"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a5a20d5843886d34ff8c57424cc65a1deda4375729cbca4cb6b3353f3ce4142"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34277a29f5303d54ec6468fb525d99c99938607bc96b8d72d675dee2b9f5bf1d"}, - {file = "websockets-14.2-cp311-cp311-win32.whl", hash = "sha256:02687db35dbc7d25fd541a602b5f8e451a238ffa033030b172ff86a93cb5dc2a"}, - {file = "websockets-14.2-cp311-cp311-win_amd64.whl", hash = "sha256:862e9967b46c07d4dcd2532e9e8e3c2825e004ffbf91a5ef9dde519ee2effb0b"}, - {file = "websockets-14.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f20522e624d7ffbdbe259c6b6a65d73c895045f76a93719aa10cd93b3de100c"}, - {file = "websockets-14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:647b573f7d3ada919fd60e64d533409a79dcf1ea21daeb4542d1d996519ca967"}, - {file = "websockets-14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af99a38e49f66be5a64b1e890208ad026cda49355661549c507152113049990"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091ab63dfc8cea748cc22c1db2814eadb77ccbf82829bac6b2fbe3401d548eda"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b374e8953ad477d17e4851cdc66d83fdc2db88d9e73abf755c94510ebddceb95"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a39d7eceeea35db85b85e1169011bb4321c32e673920ae9c1b6e0978590012a3"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6f3efd47ffd0d12080594f434faf1cd2549b31e54870b8470b28cc1d3817d9"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:065ce275e7c4ffb42cb738dd6b20726ac26ac9ad0a2a48e33ca632351a737267"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9d0e53530ba7b8b5e389c02282f9d2aa47581514bd6049d3a7cffe1385cf5fe"}, - {file = "websockets-14.2-cp312-cp312-win32.whl", hash = "sha256:20e6dd0984d7ca3037afcb4494e48c74ffb51e8013cac71cf607fffe11df7205"}, - {file = "websockets-14.2-cp312-cp312-win_amd64.whl", hash = "sha256:44bba1a956c2c9d268bdcdf234d5e5ff4c9b6dc3e300545cbe99af59dda9dcce"}, - {file = "websockets-14.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f1372e511c7409a542291bce92d6c83320e02c9cf392223272287ce55bc224e"}, - {file = "websockets-14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da98b72009836179bb596a92297b1a61bb5a830c0e483a7d0766d45070a08ad"}, - {file = "websockets-14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8a86a269759026d2bde227652b87be79f8a734e582debf64c9d302faa1e9f03"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86cf1aaeca909bf6815ea714d5c5736c8d6dd3a13770e885aafe062ecbd04f1f"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b0f6c3ba3b1240f602ebb3971d45b02cc12bd1845466dd783496b3b05783a5"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c3e101c246aa85bc8534e495952e2ca208bd87994650b90a23d745902db9a"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eabdb28b972f3729348e632ab08f2a7b616c7e53d5414c12108c29972e655b20"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2066dc4cbcc19f32c12a5a0e8cc1b7ac734e5b64ac0a325ff8353451c4b15ef2"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab95d357cd471df61873dadf66dd05dd4709cae001dd6342edafc8dc6382f307"}, - {file = "websockets-14.2-cp313-cp313-win32.whl", hash = "sha256:a9e72fb63e5f3feacdcf5b4ff53199ec8c18d66e325c34ee4c551ca748623bbc"}, - {file = "websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f"}, - {file = "websockets-14.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7cd5706caec1686c5d233bc76243ff64b1c0dc445339bd538f30547e787c11fe"}, - {file = "websockets-14.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec607328ce95a2f12b595f7ae4c5d71bf502212bddcea528290b35c286932b12"}, - {file = "websockets-14.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da85651270c6bfb630136423037dd4975199e5d4114cae6d3066641adcc9d1c7"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ecadc7ce90accf39903815697917643f5b7cfb73c96702318a096c00aa71f5"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1979bee04af6a78608024bad6dfcc0cc930ce819f9e10342a29a05b5320355d0"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dddacad58e2614a24938a50b85969d56f88e620e3f897b7d80ac0d8a5800258"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:89a71173caaf75fa71a09a5f614f450ba3ec84ad9fca47cb2422a860676716f0"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6af6a4b26eea4fc06c6818a6b962a952441e0e39548b44773502761ded8cc1d4"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:80c8efa38957f20bba0117b48737993643204645e9ec45512579132508477cfc"}, - {file = "websockets-14.2-cp39-cp39-win32.whl", hash = "sha256:2e20c5f517e2163d76e2729104abc42639c41cf91f7b1839295be43302713661"}, - {file = "websockets-14.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4c8cef610e8d7c70dea92e62b6814a8cd24fbd01d7103cc89308d2bfe1659ef"}, - {file = "websockets-14.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d7d9cafbccba46e768be8a8ad4635fa3eae1ffac4c6e7cb4eb276ba41297ed29"}, - {file = "websockets-14.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c76193c1c044bd1e9b3316dcc34b174bbf9664598791e6fb606d8d29000e070c"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd475a974d5352390baf865309fe37dec6831aafc3014ffac1eea99e84e83fc2"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6c0097a41968b2e2b54ed3424739aab0b762ca92af2379f152c1aef0187e1c"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7ff794c8b36bc402f2e07c0b2ceb4a2424147ed4785ff03e2a7af03711d60a"}, - {file = "websockets-14.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dec254fcabc7bd488dab64846f588fc5b6fe0d78f641180030f8ea27b76d72c3"}, - {file = "websockets-14.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bbe03eb853e17fd5b15448328b4ec7fb2407d45fb0245036d06a3af251f8e48f"}, - {file = "websockets-14.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3c4aa3428b904d5404a0ed85f3644d37e2cb25996b7f096d77caeb0e96a3b42"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:577a4cebf1ceaf0b65ffc42c54856214165fb8ceeba3935852fc33f6b0c55e7f"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad1c1d02357b7665e700eca43a31d52814ad9ad9b89b58118bdabc365454b574"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f390024a47d904613577df83ba700bd189eedc09c57af0a904e5c39624621270"}, - {file = "websockets-14.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3c1426c021c38cf92b453cdf371228d3430acd775edee6bac5a4d577efc72365"}, - {file = "websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b"}, - {file = "websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5"}, -] - [[package]] name = "wheel" version = "0.45.1" description = "A built-package format for Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, @@ -2882,6 +3047,8 @@ version = "1.17.2" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, @@ -2970,6 +3137,8 @@ version = "1.2.0" description = "WebSockets state-machine based protocol implementation" optional = false python-versions = ">=3.7.0" +groups = ["main", "dev"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, @@ -2984,6 +3153,8 @@ version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version <= \"3.11\" or python_full_version < \"3.10.2\"" files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, @@ -2998,6 +3169,6 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", type = ["pytest-mypy"] [metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "06172aec112c6b3a49ffa42eadf09cb66408df76bcfcec8439396590f9a4c8d5" +lock-version = "2.1" +python-versions = ">=3.10, <4.0" +content-hash = "822150bcbf41e5cbb61da0a059b41d8971e3c6c974c8af4be7ef55126648aea1" diff --git a/pyproject.toml b/pyproject.toml index ea3905f9c..966cc5e2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ keywords = ["web", "framework"] classifiers = ["Development Status :: 4 - Beta"] [tool.poetry.dependencies] -python = "^3.10" +python = ">=3.10, <4.0" fastapi = ">=0.96.0,!=0.111.0,!=0.111.1" gunicorn = ">=20.1.0,<24.0" jinja2 = ">=3.1.2,<4.0" @@ -71,6 +71,7 @@ selenium = ">=4.11.0,<5.0" pytest-benchmark = ">=4.0.0,<6.0" playwright = ">=1.46.0" pytest-playwright = ">=0.5.1" +pytest-codspeed = "^3.1.2" [tool.poetry.scripts] reflex = "reflex.reflex:cli" @@ -81,22 +82,21 @@ build-backend = "poetry.core.masonry.api" [tool.pyright] reportIncompatibleMethodOverride = false -reportIncompatibleVariableOverride = false [tool.ruff] -target-version = "py39" +target-version = "py310" output-format = "concise" lint.isort.split-on-trailing-comma = false -lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "N", "PERF", "PTH", "RUF", "SIM", "T", "TRY", "W"] +lint.select = ["ANN001","B", "C4", "D", "E", "ERA", "F", "FURB", "I", "N", "PERF", "PGH", "PTH", "RUF", "SIM", "T", "TRY", "W"] lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012", "TRY0"] lint.pydocstyle.convention = "google" [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] -"tests/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T", "N"] -"benchmarks/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T", "N"] +"tests/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"] +"benchmarks/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"] "reflex/.templates/*.py" = ["D100", "D103", "D104"] -"*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N"] +"*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"] "pyi_generator.py" = ["N802"] "reflex/constants/*.py" = ["N"] "*/blank.py" = ["I001"] diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index e5161a6dd..33bae9416 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -106,6 +106,18 @@ export const getBackendURL = (url_str) => { return endpoint; }; +/** + * Check if the backend is disabled. + * + * @returns True if the backend is disabled, false otherwise. + */ +export const isBackendDisabled = () => { + const cookie = document.cookie + .split("; ") + .find((row) => row.startsWith("backend-enabled=")); + return cookie !== undefined && cookie.split("=")[1] == "false"; +}; + /** * Determine if any event in the event queue is stateful. * @@ -405,7 +417,7 @@ export const connect = async ( socket.current = io(endpoint.href, { path: endpoint["pathname"], transports: transports, - protocols: env.TEST_MODE ? undefined : [reflexEnvironment.version], + protocols: [reflexEnvironment.version], autoUnref: false, }); // Ensure undefined fields in events are sent as null instead of removed @@ -812,7 +824,7 @@ export const useEventLoop = ( return; } // only use websockets if state is present - if (Object.keys(initialState).length > 1) { + if (Object.keys(initialState).length > 1 && !isBackendDisabled()) { // Initialize the websocket connection. if (!socket.current) { connect( diff --git a/reflex/__init__.py b/reflex/__init__.py index 72089aca0..3209b505e 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -84,6 +84,9 @@ In the example above, you will be able to do `rx.list` from __future__ import annotations +from types import ModuleType +from typing import Any + from reflex.utils import ( compat, # for side-effects lazy_loader, @@ -365,5 +368,5 @@ getattr, __dir__, __all__ = lazy_loader.attach( ) -def __getattr__(name): +def __getattr__(name: ModuleType | Any): return getattr(name) diff --git a/reflex/__init__.pyi b/reflex/__init__.pyi index 1f9b4ecd8..5c80269ad 100644 --- a/reflex/__init__.pyi +++ b/reflex/__init__.pyi @@ -131,7 +131,7 @@ from .components.radix.themes.layout.container import container as container from .components.radix.themes.layout.flex import flex as flex from .components.radix.themes.layout.grid import grid as grid from .components.radix.themes.layout.list import list_item as list_item -from .components.radix.themes.layout.list import list_ns as list # noqa +from .components.radix.themes.layout.list import list_ns as list # noqa: F401 from .components.radix.themes.layout.list import ordered_list as ordered_list from .components.radix.themes.layout.list import unordered_list as unordered_list from .components.radix.themes.layout.section import section as section diff --git a/reflex/app.py b/reflex/app.py index 5ee424719..ad123a655 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -27,6 +27,7 @@ from typing import ( Dict, Generic, List, + MutableMapping, Optional, Set, Type, @@ -58,7 +59,11 @@ from reflex.components.component import ( ComponentStyle, evaluate_style_namespaces, ) -from reflex.components.core.banner import connection_pulser, connection_toaster +from reflex.components.core.banner import ( + backend_disabled, + connection_pulser, + connection_toaster, +) from reflex.components.core.breakpoints import set_breakpoints from reflex.components.core.client_side_routing import ( Default404Page, @@ -145,7 +150,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec: position="top-center", id="backend_error", style={"width": "500px"}, - ) # type: ignore + ) # pyright: ignore [reportReturnType] else: error_message.insert(0, "An error occurred.") return window_alert("\n".join(error_message)) @@ -157,9 +162,12 @@ def default_overlay_component() -> Component: Returns: The default overlay_component, which is a connection_modal. """ + config = get_config() + return Fragment.create( connection_pulser(), connection_toaster(), + *([backend_disabled()] if config.is_reflex_cloud else []), *codespaces.codespaces_auto_redirect(), ) @@ -405,7 +413,36 @@ class App(MiddlewareMixin, LifespanMixin): self.sio.register_namespace(self.event_namespace) # Mount the socket app with the API. if self.api: - self.api.mount(str(constants.Endpoint.EVENT), socket_app) + + class HeaderMiddleware: + def __init__(self, app: ASGIApp): + self.app = app + + async def __call__( + self, scope: MutableMapping[str, Any], receive: Any, send: Callable + ): + original_send = send + + async def modified_send(message: dict): + if message["type"] == "websocket.accept": + if scope.get("subprotocols"): + # The following *does* say "subprotocol" instead of "subprotocols", intentionally. + message["subprotocol"] = scope["subprotocols"][0] + + headers = dict(message.get("headers", [])) + header_key = b"sec-websocket-protocol" + if subprotocol := headers.get(header_key): + message["headers"] = [ + *message.get("headers", []), + (header_key, subprotocol), + ] + + return await original_send(message) + + return await self.app(scope, receive, modified_send) + + socket_app_with_headers = HeaderMiddleware(socket_app) + self.api.mount(str(constants.Endpoint.EVENT), socket_app_with_headers) # Check the exception handlers self._validate_exception_handlers() @@ -648,7 +685,10 @@ class App(MiddlewareMixin, LifespanMixin): for route in self._pages: replaced_route = replace_brackets_with_keywords(route) for rw, r, nr in zip( - replaced_route.split("/"), route.split("/"), new_route.split("/") + replaced_route.split("/"), + route.split("/"), + new_route.split("/"), + strict=False, ): 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 @@ -679,8 +719,8 @@ class App(MiddlewareMixin, LifespanMixin): Args: component: The component to display at the page. title: The title of the page. - description: The description of the page. image: The image to display on the page. + description: The description of the page. on_load: The event handler(s) that will be called each time the page load. meta: The metadata of the page. """ @@ -747,7 +787,7 @@ class App(MiddlewareMixin, LifespanMixin): frontend_packages = get_config().frontend_packages _frontend_packages = [] for package in frontend_packages: - if package in (get_config().tailwind or {}).get("plugins", []): # type: ignore + if package in (get_config().tailwind or {}).get("plugins", []): console.warn( f"Tailwind packages are inferred from 'plugins', remove `{package}` from `frontend_packages`" ) @@ -992,7 +1032,7 @@ class App(MiddlewareMixin, LifespanMixin): compiler.compile_document_root( self.head_components, html_lang=self.html_lang, - html_custom_attrs=self.html_custom_attrs, # type: ignore + html_custom_attrs=self.html_custom_attrs, # pyright: ignore [reportArgumentType] ) ) @@ -1015,7 +1055,7 @@ class App(MiddlewareMixin, LifespanMixin): max_workers=environment.REFLEX_COMPILE_THREADS.get() or None ) - for route, component in zip(self._pages, page_components): + for route, component in zip(self._pages, page_components, strict=True): ExecutorSafeFunctions.COMPONENTS[route] = component ExecutorSafeFunctions.STATE = self._state @@ -1023,7 +1063,7 @@ class App(MiddlewareMixin, LifespanMixin): with executor: result_futures = [] - def _submit_work(fn, *args, **kwargs): + def _submit_work(fn: Callable, *args, **kwargs): f = executor.submit(fn, *args, **kwargs) result_futures.append(f) @@ -1212,6 +1252,7 @@ class App(MiddlewareMixin, LifespanMixin): frontend_arg_spec, backend_arg_spec, ], + strict=True, ): if hasattr(handler_fn, "__name__"): _fn_name = handler_fn.__name__ @@ -1353,15 +1394,14 @@ async def process( if app._process_background(state, event) is not None: # `final=True` allows the frontend send more events immediately. yield StateUpdate(final=True) - return + else: + # Process the event synchronously. + async for update in state._process(event): + # Postprocess the event. + update = await app._postprocess(state, event, 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 + # Yield the update. + yield update except Exception as ex: telemetry.send_error(ex, context="backend") @@ -1556,20 +1596,20 @@ class EventNamespace(AsyncNamespace): self.sid_to_token = {} self.app = app - def on_connect(self, sid, environ): + def on_connect(self, sid: str, environ: dict): """Event for when the websocket is connected. Args: sid: The Socket.IO session id. environ: The request information, including HTTP headers. """ - subprotocol = environ.get("HTTP_SEC_WEBSOCKET_PROTOCOL", None) + subprotocol = environ.get("HTTP_SEC_WEBSOCKET_PROTOCOL") if subprotocol and subprotocol != constants.Reflex.VERSION: console.warn( f"Frontend version {subprotocol} for session {sid} does not match the backend version {constants.Reflex.VERSION}." ) - def on_disconnect(self, sid): + def on_disconnect(self, sid: str): """Event for when the websocket disconnects. Args: @@ -1591,7 +1631,7 @@ class EventNamespace(AsyncNamespace): self.emit(str(constants.SocketEvent.EVENT), update, to=sid) ) - async def on_event(self, sid, data): + async def on_event(self, sid: str, data: Any): """Event for receiving front-end websocket events. Raises: @@ -1658,7 +1698,7 @@ class EventNamespace(AsyncNamespace): # Emit the update from processing the event. await self.emit_update(update=update, sid=sid) - async def on_ping(self, sid): + async def on_ping(self, sid: str): """Event for testing the API endpoint. Args: diff --git a/reflex/app_mixins/lifespan.py b/reflex/app_mixins/lifespan.py index 9403e0f35..50b90f25c 100644 --- a/reflex/app_mixins/lifespan.py +++ b/reflex/app_mixins/lifespan.py @@ -32,7 +32,7 @@ class LifespanMixin(AppMixin): try: async with contextlib.AsyncExitStack() as stack: for task in self.lifespan_tasks: - run_msg = f"Started lifespan task: {task.__name__} as {{type}}" # type: ignore + run_msg = f"Started lifespan task: {task.__name__} as {{type}}" # pyright: ignore [reportAttributeAccessIssue] if isinstance(task, asyncio.Task): running_tasks.append(task) else: @@ -61,7 +61,7 @@ class LifespanMixin(AppMixin): Args: task: The task to register. - task_kwargs: The kwargs of the task. + **task_kwargs: The kwargs of the task. Raises: InvalidLifespanTaskTypeError: If the task is a generator function. @@ -73,7 +73,7 @@ class LifespanMixin(AppMixin): if task_kwargs: original_task = task - task = functools.partial(task, **task_kwargs) # type: ignore - functools.update_wrapper(task, original_task) # type: ignore - self.lifespan_tasks.add(task) # type: ignore - console.debug(f"Registered lifespan task: {task.__name__}") # type: ignore + task = functools.partial(task, **task_kwargs) # pyright: ignore [reportArgumentType] + functools.update_wrapper(task, original_task) # pyright: ignore [reportArgumentType] + self.lifespan_tasks.add(task) + console.debug(f"Registered lifespan task: {task.__name__}") # pyright: ignore [reportAttributeAccessIssue] diff --git a/reflex/app_mixins/middleware.py b/reflex/app_mixins/middleware.py index 30593d9ae..c81fd7806 100644 --- a/reflex/app_mixins/middleware.py +++ b/reflex/app_mixins/middleware.py @@ -53,11 +53,11 @@ class MiddlewareMixin(AppMixin): """ for middleware in self.middleware: if asyncio.iscoroutinefunction(middleware.preprocess): - out = await middleware.preprocess(app=self, state=state, event=event) # type: ignore + out = await middleware.preprocess(app=self, state=state, event=event) # pyright: ignore [reportArgumentType] else: - out = middleware.preprocess(app=self, state=state, event=event) # type: ignore + out = middleware.preprocess(app=self, state=state, event=event) # pyright: ignore [reportArgumentType] if out is not None: - return out # type: ignore + return out # pyright: ignore [reportReturnType] async def _postprocess( self, state: BaseState, event: Event, update: StateUpdate @@ -78,18 +78,18 @@ class MiddlewareMixin(AppMixin): for middleware in self.middleware: if asyncio.iscoroutinefunction(middleware.postprocess): out = await middleware.postprocess( - app=self, # type: ignore + app=self, # pyright: ignore [reportArgumentType] state=state, event=event, update=update, ) else: out = middleware.postprocess( - app=self, # type: ignore + app=self, # pyright: ignore [reportArgumentType] state=state, event=event, update=update, ) if out is not None: - return out # type: ignore + return out # pyright: ignore [reportReturnType] return update diff --git a/reflex/base.py b/reflex/base.py index 1879d76d8..c900f0039 100644 --- a/reflex/base.py +++ b/reflex/base.py @@ -38,7 +38,7 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None # monkeypatch pydantic validate_field_name method to skip validating # shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True) -pydantic_main.validate_field_name = validate_field_name # type: ignore +pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPossiblyUnboundVariable, reportPrivateImportUsage] if TYPE_CHECKING: from reflex.vars import Var @@ -69,12 +69,12 @@ class Base(BaseModel): """ from reflex.utils.serializers import serialize - return self.__config__.json_dumps( # type: ignore + return self.__config__.json_dumps( self.dict(), default=serialize, ) - def set(self, **kwargs): + def set(self, **kwargs: Any): """Set multiple fields and return the object. Args: @@ -107,12 +107,12 @@ class Base(BaseModel): default_value: The default value of the field """ var_name = var._var_field_name - new_field = ModelField.infer( + new_field = ModelField.infer( # pyright: ignore [reportPossiblyUnboundVariable] name=var_name, value=default_value, annotation=var._var_type, class_validators=None, - config=cls.__config__, # type: ignore + config=cls.__config__, ) cls.__fields__.update({var_name: new_field}) diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index c0ba28f4b..d145e6c0b 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -2,6 +2,8 @@ from __future__ import annotations +import traceback +from datetime import datetime from pathlib import Path from typing import Any, Callable, Dict, Optional, Type, Union from urllib.parse import urlparse @@ -12,7 +14,9 @@ from reflex.vars.base import Var try: from pydantic.v1.fields import ModelField except ModuleNotFoundError: - from pydantic.fields import ModelField # type: ignore + from pydantic.fields import ( + ModelField, # pyright: ignore [reportAttributeAccessIssue] + ) from reflex import constants from reflex.components.base import ( @@ -115,7 +119,7 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]: default, rest = compile_import_statement(fields) # prevent lib from being rendered on the page if all imports are non rendered kind - if not any(f.render for f in fields): # type: ignore + if not any(f.render for f in fields): continue if not lib: @@ -163,8 +167,12 @@ def compile_state(state: Type[BaseState]) -> dict: try: initial_state = state(_reflex_internal_init=True).dict(initial=True) except Exception as e: + timestamp = datetime.now().strftime("%Y-%m-%d__%H-%M-%S") + constants.Reflex.LOGS_DIR.mkdir(parents=True, exist_ok=True) + log_path = constants.Reflex.LOGS_DIR / f"state_compile_error_{timestamp}.log" + traceback.TracebackException.from_exception(e).print(file=log_path.open("w+")) console.warn( - f"Failed to compile initial state with computed vars, excluding them: {e}" + f"Failed to compile initial state with computed vars. Error log saved to {log_path}" ) initial_state = state(_reflex_internal_init=True).dict( initial=True, include_computed=False @@ -494,7 +502,7 @@ def empty_dir(path: str | Path, keep_files: list[str] | None = None): path_ops.rm(element) -def is_valid_url(url) -> bool: +def is_valid_url(url: str) -> bool: """Check if a url is valid. Args: diff --git a/reflex/components/base/bare.py b/reflex/components/base/bare.py index b07bdfcfa..0c62f9000 100644 --- a/reflex/components/base/bare.py +++ b/reflex/components/base/bare.py @@ -31,7 +31,7 @@ class Bare(Component): return cls(contents=contents) else: contents = str(contents) if contents is not None else "" - return cls(contents=contents) # type: ignore + return cls(contents=contents) def _get_all_hooks_internal(self) -> dict[str, VarData | None]: """Include the hooks for the component. diff --git a/reflex/components/base/error_boundary.py b/reflex/components/base/error_boundary.py index f328773c2..74867a757 100644 --- a/reflex/components/base/error_boundary.py +++ b/reflex/components/base/error_boundary.py @@ -11,10 +11,11 @@ from reflex.event import EventHandler, set_clipboard from reflex.state import FrontendEventExceptionState from reflex.vars.base import Var from reflex.vars.function import ArgsFunctionOperation +from reflex.vars.object import ObjectVar def on_error_spec( - error: Var[Dict[str, str]], info: Var[Dict[str, str]] + error: ObjectVar[Dict[str, str]], info: ObjectVar[Dict[str, str]] ) -> Tuple[Var[str], Var[str]]: """The spec for the on_error event handler. diff --git a/reflex/components/base/error_boundary.pyi b/reflex/components/base/error_boundary.pyi index 2e01c7da0..8d27af0f3 100644 --- a/reflex/components/base/error_boundary.pyi +++ b/reflex/components/base/error_boundary.pyi @@ -9,9 +9,10 @@ from reflex.components.component import Component from reflex.event import BASE_STATE, EventType from reflex.style import Style from reflex.vars.base import Var +from reflex.vars.object import ObjectVar def on_error_spec( - error: Var[Dict[str, str]], info: Var[Dict[str, str]] + error: ObjectVar[Dict[str, str]], info: ObjectVar[Dict[str, str]] ) -> Tuple[Var[str], Var[str]]: ... class ErrorBoundary(Component): diff --git a/reflex/components/base/meta.py b/reflex/components/base/meta.py index 526233c8b..10264009e 100644 --- a/reflex/components/base/meta.py +++ b/reflex/components/base/meta.py @@ -53,11 +53,11 @@ class Description(Meta): """A component that displays the title of the current page.""" # The type of the description. - name: str = "description" + name: str | None = "description" class Image(Meta): """A component that displays the title of the current page.""" # The type of the image. - property: str = "og:image" + property: str | None = "og:image" diff --git a/reflex/components/component.py b/reflex/components/component.py index 8c3bc5184..d3acf69ab 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -149,7 +149,7 @@ class BaseComponent(Base, ABC): class ComponentNamespace(SimpleNamespace): """A namespace to manage components with subcomponents.""" - def __hash__(self) -> int: + def __hash__(self) -> int: # pyright: ignore [reportIncompatibleVariableOverride] """Get the hash of the namespace. Returns: @@ -425,7 +425,7 @@ class Component(BaseComponent, ABC): else: continue - def determine_key(value): + def determine_key(value: Any): # Try to create a var from the value key = value if isinstance(value, Var) else LiteralVar.create(value) @@ -461,9 +461,7 @@ class Component(BaseComponent, ABC): if types.is_union(passed_type): # We need to check all possible types in the union. passed_types = ( - arg - for arg in passed_type.__args__ # type: ignore - if arg is not type(None) + arg for arg in passed_type.__args__ if arg is not type(None) ) if ( # If the passed var is a union, check if all possible types are valid. @@ -490,7 +488,7 @@ class Component(BaseComponent, ABC): # Check if the key is an event trigger. if key in component_specific_triggers: kwargs["event_triggers"][key] = EventChain.create( - value=value, # type: ignore + value=value, args_spec=component_specific_triggers[key], key=key, ) @@ -577,7 +575,7 @@ class Component(BaseComponent, ABC): annotation = field.annotation if (metadata := getattr(annotation, "__metadata__", None)) is not None: args_spec = metadata[0] - default_triggers[field.name] = args_spec or (no_args_event_spec) # type: ignore + default_triggers[field.name] = args_spec or (no_args_event_spec) return default_triggers def __repr__(self) -> str: @@ -708,7 +706,7 @@ class Component(BaseComponent, ABC): # Filter out None props props = {key: value for key, value in props.items() if value is not None} - def validate_children(children): + def validate_children(children: tuple | list): for child in children: if isinstance(child, (tuple, list)): validate_children(child) @@ -760,7 +758,7 @@ class Component(BaseComponent, ABC): # 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 + s = base.add_style(self) if s is not None: styles.append(s) @@ -852,7 +850,7 @@ class Component(BaseComponent, ABC): else {} ) - def render(self) -> Dict: + def render(self) -> dict: """Render the component. Returns: @@ -870,7 +868,7 @@ class Component(BaseComponent, ABC): self._replace_prop_names(rendered_dict) return rendered_dict - def _replace_prop_names(self, rendered_dict) -> None: + def _replace_prop_names(self, rendered_dict: dict) -> None: """Replace the prop names in the render dictionary. Args: @@ -906,7 +904,7 @@ class Component(BaseComponent, ABC): comp_name = type(self).__name__ allowed_components = [comp.__name__ for comp in (Fragment,)] - def validate_child(child): + def validate_child(child: Any): child_name = type(child).__name__ # Iterate through the immediate children of fragment @@ -1683,7 +1681,7 @@ class CustomComponent(Component): if base_value is not None and isinstance(value, Component): self.component_props[key] = value value = base_value._replace( - merge_var_data=VarData( # type: ignore + merge_var_data=VarData( imports=value._get_all_imports(), hooks=value._get_all_hooks(), ) @@ -1716,7 +1714,7 @@ class CustomComponent(Component): return hash(self.tag) @classmethod - def get_props(cls) -> Set[str]: + def get_props(cls) -> Set[str]: # pyright: ignore [reportIncompatibleVariableOverride] """Get the props for the component. Returns: @@ -1811,7 +1809,7 @@ class CustomComponent(Component): include_children=include_children, ignore_ids=ignore_ids ) - @lru_cache(maxsize=None) # noqa + @lru_cache(maxsize=None) # noqa: B019 def get_component(self) -> Component: """Render the component. @@ -1953,7 +1951,7 @@ class StatefulComponent(BaseComponent): if not should_memoize: # Determine if any Vars have associated data. - for prop_var in component._get_vars(): + for prop_var in component._get_vars(include_children=True): if prop_var._get_all_var_data(): should_memoize = True break @@ -2323,8 +2321,8 @@ class MemoizationLeaf(Component): """ comp = super().create(*children, **props) if comp._get_all_hooks(): - comp._memoization_mode = cls._memoization_mode.copy( - update={"disposition": MemoizationDisposition.ALWAYS} + comp._memoization_mode = dataclasses.replace( + comp._memoization_mode, disposition=MemoizationDisposition.ALWAYS ) return comp @@ -2406,6 +2404,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) -> @dataclasses.dataclass( eq=False, frozen=True, + slots=True, ) class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar): """A Var that represents a Component.""" diff --git a/reflex/components/core/banner.py b/reflex/components/core/banner.py index 815510e8b..64b7874b6 100644 --- a/reflex/components/core/banner.py +++ b/reflex/components/core/banner.py @@ -5,8 +5,10 @@ from __future__ import annotations from typing import Optional from reflex.components.base.fragment import Fragment +from reflex import constants from reflex.components.component import Component from reflex.components.core.cond import cond +from reflex.components.datadisplay.logo import svg_logo from reflex.components.el.elements.typography import Div from reflex.components.lucide.icon import Icon from reflex.components.radix.themes.components.dialog import ( @@ -26,7 +28,7 @@ from reflex.vars.function import FunctionStringVar from reflex.vars.number import BooleanVar from reflex.vars.sequence import LiteralArrayVar -connect_error_var_data: VarData = VarData( # type: ignore +connect_error_var_data: VarData = VarData( imports=Imports.EVENTS, hooks={Hooks.EVENTS: None}, ) @@ -100,14 +102,14 @@ class ConnectionToaster(Toaster): """ toast_id = "websocket-error" target_url = WebsocketTargetURL.create() - props = ToastProps( # type: ignore + props = ToastProps( description=LiteralVar.create( f"Check if server is reachable at {target_url}", ), close_button=True, duration=120000, id=toast_id, - ) + ) # pyright: ignore [reportCallIssue] individual_hooks = [ f"const toast_props = {LiteralVar.create(props)!s};", @@ -117,7 +119,7 @@ class ConnectionToaster(Toaster): _var_data=VarData( imports={ "react": ["useEffect", "useState"], - **dict(target_url._get_all_var_data().imports), # type: ignore + **dict(target_url._get_all_var_data().imports), # pyright: ignore [reportArgumentType, reportOptionalMemberAccess] } ), ).call( @@ -296,7 +298,84 @@ class ConnectionPulser(Div): ) +class BackendDisabled(Div): + """A component that displays a message when the backend is disabled.""" + + @classmethod + def create(cls, **props) -> Component: + """Create a backend disabled component. + + Args: + **props: The properties of the component. + + Returns: + The backend disabled component. + """ + import reflex as rx + + is_backend_disabled = Var( + "backendDisabled", + _var_type=bool, + _var_data=VarData( + hooks={ + "const [backendDisabled, setBackendDisabled] = useState(false);": None, + "useEffect(() => { setBackendDisabled(isBackendDisabled()); }, []);": None, + }, + imports={ + f"$/{constants.Dirs.STATE_PATH}": [ + ImportVar(tag="isBackendDisabled") + ], + }, + ), + ) + + return super().create( + rx.cond( + is_backend_disabled, + rx.box( + rx.box( + rx.card( + rx.vstack( + svg_logo(), + rx.text( + "You ran out of compute credits.", + ), + rx.callout( + rx.fragment( + "Please upgrade your plan or raise your compute credits at ", + rx.link( + "Reflex Cloud.", + href="https://cloud.reflex.dev/", + ), + ), + width="100%", + icon="info", + variant="surface", + ), + ), + font_size="20px", + font_family='"Inter", "Helvetica", "Arial", sans-serif', + variant="classic", + ), + position="fixed", + top="50%", + left="50%", + transform="translate(-50%, -50%)", + width="40ch", + max_width="90vw", + ), + position="fixed", + z_index=9999, + backdrop_filter="grayscale(1) blur(5px)", + width="100dvw", + height="100dvh", + ), + ) + ) + + connection_banner = ConnectionBanner.create connection_modal = ConnectionModal.create connection_toaster = ConnectionToaster.create connection_pulser = ConnectionPulser.create +backend_disabled = BackendDisabled.create diff --git a/reflex/components/core/banner.pyi b/reflex/components/core/banner.pyi index 0f10e0df1..ec7de7a6f 100644 --- a/reflex/components/core/banner.pyi +++ b/reflex/components/core/banner.pyi @@ -351,7 +351,93 @@ class ConnectionPulser(Div): """ ... +class BackendDisabled(Div): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + access_key: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + auto_capitalize: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + content_editable: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + context_menu: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + dir: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + draggable: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + enter_key_hint: Optional[ + Union[Var[Union[bool, int, str]], bool, int, str] + ] = None, + hidden: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + input_mode: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + item_prop: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + lang: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + role: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + slot: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + spell_check: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + tab_index: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + title: Optional[Union[Var[Union[bool, int, str]], bool, int, str]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None, + on_blur: Optional[EventType[[], BASE_STATE]] = None, + on_click: Optional[EventType[[], BASE_STATE]] = None, + on_context_menu: Optional[EventType[[], BASE_STATE]] = None, + on_double_click: Optional[EventType[[], BASE_STATE]] = None, + on_focus: Optional[EventType[[], BASE_STATE]] = None, + on_mount: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_down: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_move: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_out: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_over: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_up: Optional[EventType[[], BASE_STATE]] = None, + on_scroll: Optional[EventType[[], BASE_STATE]] = None, + on_unmount: Optional[EventType[[], BASE_STATE]] = None, + **props, + ) -> "BackendDisabled": + """Create a backend disabled component. + + Args: + access_key: Provides a hint for generating a keyboard shortcut for the current element. + auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user. + content_editable: Indicates whether the element's content is editable. + context_menu: Defines the ID of a element which will serve as the element's context menu. + dir: Defines the text direction. Allowed values are ltr (Left-To-Right) or rtl (Right-To-Left) + draggable: Defines whether the element can be dragged. + enter_key_hint: Hints what media types the media element is able to play. + hidden: Defines whether the element is hidden. + input_mode: Defines the type of the element. + item_prop: Defines the name of the element for metadata purposes. + lang: Defines the language used in the element. + role: Defines the role of the element. + slot: Assigns a slot in a shadow DOM shadow tree to an element. + spell_check: Defines whether the element may be checked for spelling errors. + tab_index: Defines the position of the current element in the tabbing order. + title: Defines a tooltip for the element. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The properties of the component. + + Returns: + The backend disabled component. + """ + ... + connection_banner = ConnectionBanner.create connection_modal = ConnectionModal.create connection_toaster = ConnectionToaster.create connection_pulser = ConnectionPulser.create +backend_disabled = BackendDisabled.create diff --git a/reflex/components/core/breakpoints.py b/reflex/components/core/breakpoints.py index 25396ecd9..9a8ef1556 100644 --- a/reflex/components/core/breakpoints.py +++ b/reflex/components/core/breakpoints.py @@ -82,7 +82,9 @@ class Breakpoints(Dict[K, V]): return Breakpoints( { k: v - for k, v in zip(["initial", *breakpoint_names], thresholds) + for k, v in zip( + ["initial", *breakpoint_names], thresholds, strict=True + ) if v is not None } ) diff --git a/reflex/components/core/client_side_routing.py b/reflex/components/core/client_side_routing.py index a10b90de8..0fc40de5f 100644 --- a/reflex/components/core/client_side_routing.py +++ b/reflex/components/core/client_side_routing.py @@ -41,7 +41,7 @@ class ClientSideRouting(Component): return "" -def wait_for_client_redirect(component) -> Component: +def wait_for_client_redirect(component: Component) -> Component: """Wait for a redirect to occur before rendering a component. This prevents the 404 page from flashing while the redirect is happening. diff --git a/reflex/components/core/client_side_routing.pyi b/reflex/components/core/client_side_routing.pyi index 581b0e120..078698198 100644 --- a/reflex/components/core/client_side_routing.pyi +++ b/reflex/components/core/client_side_routing.pyi @@ -60,7 +60,7 @@ class ClientSideRouting(Component): """ ... -def wait_for_client_redirect(component) -> Component: ... +def wait_for_client_redirect(component: Component) -> Component: ... class Default404Page(Component): @overload diff --git a/reflex/components/core/cond.py b/reflex/components/core/cond.py index 09f2ab213..c98ae5108 100644 --- a/reflex/components/core/cond.py +++ b/reflex/components/core/cond.py @@ -59,7 +59,7 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var: @overload -def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # type: ignore +def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # pyright: ignore [reportOverlappingOverload] @overload diff --git a/reflex/components/core/debounce.py b/reflex/components/core/debounce.py index 12cc94426..1d798994d 100644 --- a/reflex/components/core/debounce.py +++ b/reflex/components/core/debounce.py @@ -28,7 +28,7 @@ class DebounceInput(Component): min_length: Var[int] # Time to wait between end of input and triggering on_change - debounce_timeout: Var[int] = DEFAULT_DEBOUNCE_TIMEOUT # type: ignore + debounce_timeout: Var[int] = Var.create(DEFAULT_DEBOUNCE_TIMEOUT) # If true, notify when Enter key is pressed force_notify_by_enter: Var[bool] diff --git a/reflex/components/core/foreach.py b/reflex/components/core/foreach.py index 1678642ea..e0eba7ef5 100644 --- a/reflex/components/core/foreach.py +++ b/reflex/components/core/foreach.py @@ -2,8 +2,10 @@ from __future__ import annotations +import functools from typing import Callable, Iterable +from reflex.utils.exceptions import UntypedVarError from reflex.vars.base import LiteralVar, Var from reflex.vars.object import ObjectVar from reflex.vars.sequence import ArrayVar diff --git a/reflex/components/core/upload.py b/reflex/components/core/upload.py index 1fb02dcc4..6c86d3c44 100644 --- a/reflex/components/core/upload.py +++ b/reflex/components/core/upload.py @@ -267,7 +267,7 @@ class Upload(MemoizationLeaf): on_drop = upload_props["on_drop"] if isinstance(on_drop, Callable): # Call the lambda to get the event chain. - on_drop = call_event_fn(on_drop, _on_drop_spec) # type: ignore + on_drop = call_event_fn(on_drop, _on_drop_spec) if isinstance(on_drop, EventSpec): # Update the provided args for direct use with on_drop. on_drop = on_drop.with_args( diff --git a/reflex/components/datadisplay/code.py b/reflex/components/datadisplay/code.py index 242e812b2..4f1eb493e 100644 --- a/reflex/components/datadisplay/code.py +++ b/reflex/components/datadisplay/code.py @@ -449,7 +449,7 @@ class CodeBlock(Component, MarkdownComponentMap): if can_copy: code = children[0] - copy_button = ( # type: ignore + copy_button = ( copy_button if copy_button is not None else Button.create( diff --git a/reflex/components/datadisplay/dataeditor.py b/reflex/components/datadisplay/dataeditor.py index b82196fe2..7ab19644c 100644 --- a/reflex/components/datadisplay/dataeditor.py +++ b/reflex/components/datadisplay/dataeditor.py @@ -165,7 +165,7 @@ class DataEditor(NoSSRComponent): tag = "DataEditor" is_default = True - library: str = "@glideapps/glide-data-grid@^6.0.3" + library: str | None = "@glideapps/glide-data-grid@^6.0.3" lib_dependencies: List[str] = [ "lodash@^4.17.21", "react-responsive-carousel@^3.2.7", @@ -321,6 +321,8 @@ class DataEditor(NoSSRComponent): Returns: The import dict. """ + if self.library is None: + return {} return { "": f"{format.format_library_name(self.library)}/dist/index.css", self.library: "GridCellKind", @@ -343,7 +345,7 @@ class DataEditor(NoSSRComponent): data_callback = self.get_cell_content._js_expr else: data_callback = f"getData_{editor_id}" - self.get_cell_content = Var(_js_expr=data_callback) # type: ignore + self.get_cell_content = Var(_js_expr=data_callback) code = [f"function {data_callback}([col, row])" "{"] @@ -385,7 +387,8 @@ class DataEditor(NoSSRComponent): raise ValueError( "DataEditor data must be an ArrayVar if rows is not provided." ) - props["rows"] = data.length() if isinstance(data, Var) else len(data) + + props["rows"] = data.length() if isinstance(data, ArrayVar) else len(data) if not isinstance(columns, Var) and len(columns): if types.is_dataframe(type(data)) or ( diff --git a/reflex/components/datadisplay/logo.py b/reflex/components/datadisplay/logo.py index d960b8cee..1c4c02001 100644 --- a/reflex/components/datadisplay/logo.py +++ b/reflex/components/datadisplay/logo.py @@ -15,10 +15,8 @@ def svg_logo(color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "whi The Reflex logo SVG. """ - def logo_path(d): - return rx.el.svg.path( - d=d, - ) + def logo_path(d: str): + return rx.el.svg.path(d=d) paths = [ "M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z", diff --git a/reflex/components/datadisplay/shiki_code_block.py b/reflex/components/datadisplay/shiki_code_block.py index 096f2f89a..1930dd02e 100644 --- a/reflex/components/datadisplay/shiki_code_block.py +++ b/reflex/components/datadisplay/shiki_code_block.py @@ -602,7 +602,7 @@ class ShikiCodeBlock(Component, MarkdownComponentMap): transformer_styles = {} # Collect styles from transformers and wrapper - for transformer in code_block.transformers._var_value: # type: ignore + for transformer in code_block.transformers._var_value: # pyright: ignore [reportAttributeAccessIssue] if isinstance(transformer, ShikiBaseTransformers) and transformer.style: transformer_styles.update(transformer.style) transformer_styles.update(code_wrapper_props.pop("style", {})) @@ -621,18 +621,22 @@ class ShikiCodeBlock(Component, MarkdownComponentMap): Returns: Imports for the component. + + Raises: + ValueError: If the transformers are not of type LiteralVar. """ imports = defaultdict(list) + if not isinstance(self.transformers, LiteralVar): + raise ValueError( + f"transformers should be a LiteralVar type. Got {type(self.transformers)} instead." + ) for transformer in self.transformers._var_value: if isinstance(transformer, ShikiBaseTransformers): imports[transformer.library].extend( [ImportVar(tag=str(fn)) for fn in transformer.fns] ) - ( + if transformer.library not in self.lib_dependencies: self.lib_dependencies.append(transformer.library) - if transformer.library not in self.lib_dependencies - else None - ) return imports @classmethod @@ -653,8 +657,9 @@ class ShikiCodeBlock(Component, MarkdownComponentMap): raise ValueError( f"the function names should be str names of functions in the specified transformer: {library!r}" ) - return ShikiBaseTransformers( # type: ignore - library=library, fns=[FunctionStringVar.create(fn) for fn in fns] + return ShikiBaseTransformers( + library=library, + fns=[FunctionStringVar.create(fn) for fn in fns], # pyright: ignore [reportCallIssue] ) def _render(self, props: dict[str, Any] | None = None): @@ -757,13 +762,13 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock): if can_copy: code = children[0] - copy_button = ( # type: ignore + copy_button = ( copy_button if copy_button is not None else Button.create( Icon.create(tag="copy", size=16, color=color("gray", 11)), on_click=[ - set_clipboard(cls._strip_transformer_triggers(code)), # type: ignore + set_clipboard(cls._strip_transformer_triggers(code)), copy_script(), ], style=Style( diff --git a/reflex/components/el/constants/reflex.py b/reflex/components/el/constants/reflex.py index 05c298325..199edf569 100644 --- a/reflex/components/el/constants/reflex.py +++ b/reflex/components/el/constants/reflex.py @@ -48,4 +48,4 @@ PROP_TO_ELEMENTS = { ELEMENT_TO_PROPS = defaultdict(list) for prop, elements in PROP_TO_ELEMENTS.items(): for el in elements: - ELEMENT_TO_PROPS[el].append(prop) # type: ignore + ELEMENT_TO_PROPS[el].append(prop) diff --git a/reflex/components/el/element.py b/reflex/components/el/element.py index 213cea65a..c9a58b1f6 100644 --- a/reflex/components/el/element.py +++ b/reflex/components/el/element.py @@ -6,7 +6,7 @@ from reflex.components.component import Component class Element(Component): """The base class for all raw HTML elements.""" - def __eq__(self, other): + def __eq__(self, other: object): """Two elements are equal if they have the same tag. Args: diff --git a/reflex/components/el/elements/forms.py b/reflex/components/el/elements/forms.py index 3e2235721..51ad201b2 100644 --- a/reflex/components/el/elements/forms.py +++ b/reflex/components/el/elements/forms.py @@ -153,7 +153,7 @@ class Form(BaseHTML): target: Var[Union[str, int, bool]] # If true, the form will be cleared after submit. - reset_on_submit: Var[bool] = False # type: ignore + reset_on_submit: Var[bool] = Var.create(False) # The name used to make this form's submit handler function unique. handle_submit_unique_name: Var[str] @@ -405,7 +405,7 @@ class Input(BaseHTML): (value_var := Var.create(value))._var_type ): props["value"] = ternary_operation( - (value_var != Var.create(None)) # pyright: ignore [reportGeneralTypeIssues] + (value_var != Var.create(None)) # pyright: ignore [reportArgumentType] & (value_var != Var(_js_expr="undefined")), value, Var.create(""), diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py index bf94f5f70..53ce22494 100644 --- a/reflex/components/markdown/markdown.py +++ b/reflex/components/markdown/markdown.py @@ -8,7 +8,7 @@ from functools import lru_cache from hashlib import md5 from typing import Any, Callable, Dict, Sequence, Union -from reflex.components.component import Component, CustomComponent +from reflex.components.component import BaseComponent, Component, CustomComponent from reflex.components.tags.tag import Tag from reflex.utils import types from reflex.utils.imports import ImportDict, ImportVar @@ -65,8 +65,8 @@ def get_base_component_map() -> dict[str, Callable]: "h5": lambda value: Heading.create(value, as_="h5", size="2", margin_y="0.5em"), "h6": lambda value: Heading.create(value, as_="h6", size="1", margin_y="0.5em"), "p": lambda value: Text.create(value, margin_y="1em"), - "ul": lambda value: UnorderedList.create(value, margin_y="1em"), # type: ignore - "ol": lambda value: OrderedList.create(value, margin_y="1em"), # type: ignore + "ul": lambda value: UnorderedList.create(value, margin_y="1em"), + "ol": lambda value: OrderedList.create(value, margin_y="1em"), "li": lambda value: ListItem.create(value, margin_y="0.5em"), "a": lambda value: Link.create(value), "code": lambda value: Code.create(value), @@ -236,7 +236,7 @@ class Markdown(Component): ), }, *[ - component(_MOCK_ARG)._get_all_imports() # type: ignore + component(_MOCK_ARG)._get_all_imports() for component in self.component_map.values() ], ] @@ -327,7 +327,7 @@ const {_LANGUAGE!s} = match ? match[1] : ''; if tag != "codeblock" # For codeblock, the mapping for some cases returns an array of elements. Let's join them into a string. else ternary_operation( - ARRAY_ISARRAY.call(_CHILDREN), # type: ignore + ARRAY_ISARRAY.call(_CHILDREN), # pyright: ignore [reportArgumentType] _CHILDREN.to(list).join("\n"), _CHILDREN, ).to(str) @@ -379,7 +379,9 @@ const {_LANGUAGE!s} = match ? match[1] : ''; # fallback to the default fn Var creation if the component is not a MarkdownComponentMap. return MarkdownComponentMap.create_map_fn_var(fn_body=formatted_component) - def _get_map_fn_custom_code_from_children(self, component) -> list[str]: + def _get_map_fn_custom_code_from_children( + self, component: BaseComponent + ) -> list[str]: """Recursively get markdown custom code from children components. Args: @@ -409,7 +411,7 @@ const {_LANGUAGE!s} = match ? match[1] : ''; return custom_code_list @staticmethod - def _component_map_hash(component_map) -> str: + def _component_map_hash(component_map: dict) -> str: inp = str( {tag: component(_MOCK_ARG) for tag, component in component_map.items()} ).encode() @@ -425,7 +427,7 @@ const {_LANGUAGE!s} = match ? match[1] : ''; for _component in self.component_map.values(): comp = _component(_MOCK_ARG) hooks.update(comp._get_all_hooks()) - formatted_hooks = MACROS.module.renderHooks(hooks) # type: ignore + formatted_hooks = MACROS.module.renderHooks(hooks) # pyright: ignore [reportAttributeAccessIssue] return f""" function {self._get_component_map_name()} () {{ {formatted_hooks} diff --git a/reflex/components/moment/moment.py b/reflex/components/moment/moment.py index 80940d228..a5fe79f07 100644 --- a/reflex/components/moment/moment.py +++ b/reflex/components/moment/moment.py @@ -28,9 +28,9 @@ class MomentDelta: class Moment(NoSSRComponent): """The Moment component.""" - tag: str = "Moment" + tag: str | None = "Moment" is_default = True - library: str = "react-moment" + library: str | None = "react-moment" lib_dependencies: List[str] = ["moment"] # How often the date update (how often time update / 0 to disable). diff --git a/reflex/components/next/image.py b/reflex/components/next/image.py index 00821ddaf..20ba5a304 100644 --- a/reflex/components/next/image.py +++ b/reflex/components/next/image.py @@ -1,5 +1,7 @@ """Image component from next/image.""" +from __future__ import annotations + from typing import Any, Literal, Optional, Union from reflex.event import EventHandler, no_args_event_spec @@ -93,7 +95,7 @@ class Image(NextComponent): style = props.get("style", {}) - def check_prop_type(prop_name, prop_value): + def check_prop_type(prop_name: str, prop_value: int | str | None): if types.check_prop_in_allowed_types(prop_value, allowed_types=[int]): props[prop_name] = prop_value diff --git a/reflex/components/next/link.py b/reflex/components/next/link.py index 0f7c81296..187618a09 100644 --- a/reflex/components/next/link.py +++ b/reflex/components/next/link.py @@ -17,4 +17,4 @@ class NextLink(Component): href: Var[str] # Whether to pass the href prop to the child. - pass_href: Var[bool] = True # type: ignore + pass_href: Var[bool] = Var.create(True) diff --git a/reflex/components/plotly/plotly.py b/reflex/components/plotly/plotly.py index 68efd4545..c85423d35 100644 --- a/reflex/components/plotly/plotly.py +++ b/reflex/components/plotly/plotly.py @@ -18,8 +18,8 @@ try: Template = layout.Template except ImportError: console.warn("Plotly is not installed. Please run `pip install plotly`.") - Figure = Any # type: ignore - Template = Any # type: ignore + Figure = Any + Template = Any def _event_points_data_signature(e0: Var) -> Tuple[Var[List[Point]]]: @@ -102,13 +102,13 @@ class Plotly(NoSSRComponent): is_default = True # The figure to display. This can be a plotly figure or a plotly data json. - data: Var[Figure] # type: ignore + data: Var[Figure] # pyright: ignore [reportInvalidTypeForm] # The layout of the graph. layout: Var[Dict] # The template for visual appearance of the graph. - template: Var[Template] # type: ignore + template: Var[Template] # pyright: ignore [reportInvalidTypeForm] # The config of the graph. config: Var[Dict] diff --git a/reflex/components/props.py b/reflex/components/props.py index adce134fc..779e714d9 100644 --- a/reflex/components/props.py +++ b/reflex/components/props.py @@ -48,7 +48,7 @@ class PropsBase(Base): class NoExtrasAllowedProps(Base): """A class that holds props to be passed or applied to a component with no extra props allowed.""" - def __init__(self, component_name=None, **kwargs): + def __init__(self, component_name: str | None = None, **kwargs): """Initialize the props. Args: @@ -62,13 +62,13 @@ class NoExtrasAllowedProps(Base): try: super().__init__(**kwargs) except ValidationError as e: - invalid_fields = ", ".join([error["loc"][0] for error in e.errors()]) # type: ignore + invalid_fields = ", ".join([error["loc"][0] for error in e.errors()]) # pyright: ignore [reportCallIssue, reportArgumentType] supported_props_str = ", ".join(f'"{field}"' for field in self.get_fields()) raise InvalidPropValueError( f"Invalid prop(s) {invalid_fields} for {component_name!r}. Supported props are {supported_props_str}" ) from None - class Config: + class Config: # pyright: ignore [reportIncompatibleVariableOverride] """Pydantic config.""" arbitrary_types_allowed = True diff --git a/reflex/components/radix/__init__.pyi b/reflex/components/radix/__init__.pyi index f4e81666a..9a16627b5 100644 --- a/reflex/components/radix/__init__.pyi +++ b/reflex/components/radix/__init__.pyi @@ -55,7 +55,7 @@ from .themes.layout.container import container as container from .themes.layout.flex import flex as flex from .themes.layout.grid import grid as grid from .themes.layout.list import list_item as list_item -from .themes.layout.list import list_ns as list # noqa +from .themes.layout.list import list_ns as list # noqa: F401 from .themes.layout.list import ordered_list as ordered_list from .themes.layout.list import unordered_list as unordered_list from .themes.layout.section import section as section diff --git a/reflex/components/radix/primitives/accordion.py b/reflex/components/radix/primitives/accordion.py index befccb829..90a1c41f0 100644 --- a/reflex/components/radix/primitives/accordion.py +++ b/reflex/components/radix/primitives/accordion.py @@ -10,6 +10,7 @@ from reflex.components.core.cond import cond from reflex.components.lucide.icon import Icon from reflex.components.radix.primitives.base import RadixPrimitiveComponent from reflex.components.radix.themes.base import LiteralAccentColor, LiteralRadius +from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler from reflex.style import Style from reflex.vars import get_uuid_string_var @@ -196,8 +197,9 @@ class AccordionItem(AccordionComponent): # The header of the accordion item. header: Var[Union[Component, str]] + # The content of the accordion item. - content: Var[Union[Component, str]] = Var.create("") + content: Var[Union[Component, str, None]] = Var.create(None) _valid_children: List[str] = [ "AccordionHeader", @@ -341,6 +343,8 @@ class AccordionTrigger(AccordionComponent): alias = "RadixAccordionTrigger" + _memoization_mode = MemoizationMode(recursive=False) + @classmethod def create(cls, *children, **props) -> Component: """Create the Accordion trigger component. diff --git a/reflex/components/radix/primitives/accordion.pyi b/reflex/components/radix/primitives/accordion.pyi index 03208f496..447451d11 100644 --- a/reflex/components/radix/primitives/accordion.pyi +++ b/reflex/components/radix/primitives/accordion.pyi @@ -308,7 +308,9 @@ class AccordionItem(AccordionComponent): value: Optional[Union[Var[str], str]] = None, disabled: Optional[Union[Var[bool], bool]] = None, header: Optional[Union[Component, Var[Union[Component, str]], str]] = None, - content: Optional[Union[Component, Var[Union[Component, str]], str]] = None, + content: Optional[ + Union[Component, Var[Optional[Union[Component, str]]], str] + ] = None, color_scheme: Optional[ Union[ Literal[ diff --git a/reflex/components/radix/primitives/drawer.py b/reflex/components/radix/primitives/drawer.py index 534b97ac1..30d1a6ae3 100644 --- a/reflex/components/radix/primitives/drawer.py +++ b/reflex/components/radix/primitives/drawer.py @@ -10,6 +10,7 @@ from reflex.components.component import Component, ComponentNamespace from reflex.components.radix.primitives.base import RadixPrimitiveComponent from reflex.components.radix.themes.base import Theme from reflex.components.radix.themes.layout.flex import Flex +from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var @@ -83,7 +84,9 @@ class DrawerTrigger(DrawerComponent): alias = "Vaul" + tag # Defaults to true, if the first child acts as the trigger. - as_child: Var[bool] = True # type: ignore + as_child: Var[bool] = Var.create(True) + + _memoization_mode = MemoizationMode(recursive=False) @classmethod def create(cls, *children: Any, **props: Any) -> Component: diff --git a/reflex/components/radix/primitives/progress.py b/reflex/components/radix/primitives/progress.py index 72aee1038..5fcc52f1b 100644 --- a/reflex/components/radix/primitives/progress.py +++ b/reflex/components/radix/primitives/progress.py @@ -83,7 +83,7 @@ class ProgressIndicator(ProgressComponent): "&[data_state='loading']": { "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear", }, - "transform": f"translateX(calc(-100% + ({self.value} / {self.max} * 100%)))", # type: ignore + "transform": f"translateX(calc(-100% + ({self.value} / {self.max} * 100%)))", "boxShadow": "inset 0 0 0 1px var(--gray-a5)", } diff --git a/reflex/components/radix/primitives/slider.py b/reflex/components/radix/primitives/slider.py index eafdd65b4..a6f8245ff 100644 --- a/reflex/components/radix/primitives/slider.py +++ b/reflex/components/radix/primitives/slider.py @@ -30,7 +30,7 @@ def on_value_event_spec( Returns: The event handler spec. """ - return (value,) # type: ignore + return (value,) class SliderRoot(SliderComponent): diff --git a/reflex/components/radix/themes/color_mode.py b/reflex/components/radix/themes/color_mode.py index 875a53456..f370a5aa5 100644 --- a/reflex/components/radix/themes/color_mode.py +++ b/reflex/components/radix/themes/color_mode.py @@ -17,7 +17,7 @@ rx.text( from __future__ import annotations -from typing import Dict, List, Literal, Optional, Union, get_args +from typing import Any, Dict, List, Literal, Optional, Union, get_args from reflex.components.component import BaseComponent from reflex.components.core.cond import color_mode_cond, cond @@ -73,17 +73,19 @@ position_map: Dict[str, List[str]] = { # needed to inverse contains for find -def _find(const: List[str], var): +def _find(const: List[str], var: Any): return LiteralArrayVar.create(const).contains(var) -def _set_var_default(props, position, prop, default1, default2=""): +def _set_var_default( + props: dict, position: Any, prop: str, default1: str, default2: str = "" +): props.setdefault( prop, cond(_find(position_map[prop], position), default1, default2) ) -def _set_static_default(props, position, prop, default): +def _set_static_default(props: dict, position: Any, prop: str, default: str): if prop in position: props.setdefault(prop, default) @@ -110,12 +112,12 @@ class ColorModeIconButton(IconButton): Returns: The button component. """ - position = props.pop("position", None) + position: str | Var = props.pop("position", None) allow_system = props.pop("allow_system", False) # position is used to set nice defaults for positioning the icon button if isinstance(position, Var): - _set_var_default(props, position, "position", "fixed", position) # type: ignore + _set_var_default(props, position, "position", "fixed", position) # pyright: ignore [reportArgumentType] _set_var_default(props, position, "bottom", "2rem") _set_var_default(props, position, "top", "2rem") _set_var_default(props, position, "left", "2rem") @@ -137,7 +139,7 @@ class ColorModeIconButton(IconButton): if allow_system: - def color_mode_item(_color_mode): + def color_mode_item(_color_mode: str): return dropdown_menu.item( _color_mode.title(), on_click=set_color_mode(_color_mode) ) diff --git a/reflex/components/radix/themes/components/alert_dialog.py b/reflex/components/radix/themes/components/alert_dialog.py index 36d38532c..bc5e2dc7e 100644 --- a/reflex/components/radix/themes/components/alert_dialog.py +++ b/reflex/components/radix/themes/components/alert_dialog.py @@ -5,6 +5,7 @@ from typing import Literal from reflex.components.component import ComponentNamespace from reflex.components.core.breakpoints import Responsive from reflex.components.el import elements +from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var @@ -33,6 +34,8 @@ class AlertDialogTrigger(RadixThemesTriggerComponent): tag = "AlertDialog.Trigger" + _memoization_mode = MemoizationMode(recursive=False) + class AlertDialogContent(elements.Div, RadixThemesComponent): """Contains the content of the dialog. This component is based on the div element.""" diff --git a/reflex/components/radix/themes/components/card.py b/reflex/components/radix/themes/components/card.py index 30823de56..e99ea9cef 100644 --- a/reflex/components/radix/themes/components/card.py +++ b/reflex/components/radix/themes/components/card.py @@ -20,7 +20,7 @@ class Card(elements.Div, RadixThemesComponent): # Card size: "1" - "5" size: Var[Responsive[Literal["1", "2", "3", "4", "5"],]] - # Variant of Card: "solid" | "soft" | "outline" | "ghost" + # Variant of Card: "surface" | "classic" | "ghost" variant: Var[Literal["surface", "classic", "ghost"]] diff --git a/reflex/components/radix/themes/components/card.pyi b/reflex/components/radix/themes/components/card.pyi index d8ab6c06b..e515982e4 100644 --- a/reflex/components/radix/themes/components/card.pyi +++ b/reflex/components/radix/themes/components/card.pyi @@ -94,7 +94,7 @@ class Card(elements.Div, RadixThemesComponent): *children: Child components. as_child: Change the default rendered element for the one passed as a child, merging their props and behavior. size: Card size: "1" - "5" - variant: Variant of Card: "solid" | "soft" | "outline" | "ghost" + variant: Variant of Card: "surface" | "classic" | "ghost" access_key: Provides a hint for generating a keyboard shortcut for the current element. auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user. content_editable: Indicates whether the element's content is editable. diff --git a/reflex/components/radix/themes/components/context_menu.py b/reflex/components/radix/themes/components/context_menu.py index f8512a902..60d23db1a 100644 --- a/reflex/components/radix/themes/components/context_menu.py +++ b/reflex/components/radix/themes/components/context_menu.py @@ -4,6 +4,7 @@ from typing import Dict, List, Literal, Union from reflex.components.component import ComponentNamespace from reflex.components.core.breakpoints import Responsive +from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var @@ -55,6 +56,8 @@ class ContextMenuTrigger(RadixThemesComponent): _invalid_children: List[str] = ["ContextMenuContent"] + _memoization_mode = MemoizationMode(recursive=False) + class ContextMenuContent(RadixThemesComponent): """The component that pops out when the context menu is open.""" @@ -153,6 +156,8 @@ class ContextMenuSubTrigger(RadixThemesComponent): _valid_parents: List[str] = ["ContextMenuContent", "ContextMenuSub"] + _memoization_mode = MemoizationMode(recursive=False) + class ContextMenuSubContent(RadixThemesComponent): """The component that pops out when a submenu is open.""" diff --git a/reflex/components/radix/themes/components/dialog.py b/reflex/components/radix/themes/components/dialog.py index 1b7c3b532..ce6e52cb5 100644 --- a/reflex/components/radix/themes/components/dialog.py +++ b/reflex/components/radix/themes/components/dialog.py @@ -5,6 +5,7 @@ from typing import Literal from reflex.components.component import ComponentNamespace from reflex.components.core.breakpoints import Responsive from reflex.components.el import elements +from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var @@ -31,6 +32,8 @@ class DialogTrigger(RadixThemesTriggerComponent): tag = "Dialog.Trigger" + _memoization_mode = MemoizationMode(recursive=False) + class DialogTitle(RadixThemesComponent): """Title component to display inside a Dialog modal.""" diff --git a/reflex/components/radix/themes/components/dropdown_menu.py b/reflex/components/radix/themes/components/dropdown_menu.py index abce3e3bb..6d5709e11 100644 --- a/reflex/components/radix/themes/components/dropdown_menu.py +++ b/reflex/components/radix/themes/components/dropdown_menu.py @@ -4,6 +4,7 @@ from typing import Dict, List, Literal, Union from reflex.components.component import ComponentNamespace from reflex.components.core.breakpoints import Responsive +from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var @@ -60,6 +61,8 @@ class DropdownMenuTrigger(RadixThemesTriggerComponent): _invalid_children: List[str] = ["DropdownMenuContent"] + _memoization_mode = MemoizationMode(recursive=False) + class DropdownMenuContent(RadixThemesComponent): """The Dropdown Menu Content component that pops out when the dropdown menu is open.""" @@ -143,6 +146,8 @@ class DropdownMenuSubTrigger(RadixThemesTriggerComponent): _valid_parents: List[str] = ["DropdownMenuContent", "DropdownMenuSub"] + _memoization_mode = MemoizationMode(recursive=False) + class DropdownMenuSub(RadixThemesComponent): """Contains all the parts of a submenu.""" diff --git a/reflex/components/radix/themes/components/hover_card.py b/reflex/components/radix/themes/components/hover_card.py index bd5489ce6..9e7aa4688 100644 --- a/reflex/components/radix/themes/components/hover_card.py +++ b/reflex/components/radix/themes/components/hover_card.py @@ -5,6 +5,7 @@ from typing import Dict, Literal, Union from reflex.components.component import ComponentNamespace from reflex.components.core.breakpoints import Responsive from reflex.components.el import elements +from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var @@ -37,6 +38,8 @@ class HoverCardTrigger(RadixThemesTriggerComponent): tag = "HoverCard.Trigger" + _memoization_mode = MemoizationMode(recursive=False) + class HoverCardContent(elements.Div, RadixThemesComponent): """Contains the content of the open hover card.""" diff --git a/reflex/components/radix/themes/components/popover.py b/reflex/components/radix/themes/components/popover.py index bdf5f4af3..4c0542cb7 100644 --- a/reflex/components/radix/themes/components/popover.py +++ b/reflex/components/radix/themes/components/popover.py @@ -5,6 +5,7 @@ from typing import Dict, Literal, Union from reflex.components.component import ComponentNamespace from reflex.components.core.breakpoints import Responsive from reflex.components.el import elements +from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var @@ -34,6 +35,8 @@ class PopoverTrigger(RadixThemesTriggerComponent): tag = "Popover.Trigger" + _memoization_mode = MemoizationMode(recursive=False) + class PopoverContent(elements.Div, RadixThemesComponent): """Contains content to be rendered in the open popover.""" diff --git a/reflex/components/radix/themes/components/radio_cards.py b/reflex/components/radix/themes/components/radio_cards.py index e075a1ba2..4a9aefc99 100644 --- a/reflex/components/radix/themes/components/radio_cards.py +++ b/reflex/components/radix/themes/components/radio_cards.py @@ -85,6 +85,8 @@ class RadioCardsItem(RadixThemesComponent): # When true, indicates that the user must check the radio item before the owning form can be submitted. required: Var[bool] + _valid_parents: list[str] = ["RadioCardsRoot"] + class RadioCards(SimpleNamespace): """RadioCards components namespace.""" diff --git a/reflex/components/radix/themes/components/radio_group.py b/reflex/components/radix/themes/components/radio_group.py index 80b3ee10c..f34c92159 100644 --- a/reflex/components/radix/themes/components/radio_group.py +++ b/reflex/components/radix/themes/components/radio_group.py @@ -155,7 +155,7 @@ class HighLevelRadioGroup(RadixThemesComponent): if isinstance(default_value, str) or ( isinstance(default_value, Var) and default_value._var_type is str ): - default_value = LiteralVar.create(default_value) # type: ignore + default_value = LiteralVar.create(default_value) else: default_value = LiteralVar.create(default_value).to_string() diff --git a/reflex/components/radix/themes/components/select.py b/reflex/components/radix/themes/components/select.py index 45e5712bc..6ac992380 100644 --- a/reflex/components/radix/themes/components/select.py +++ b/reflex/components/radix/themes/components/select.py @@ -5,6 +5,7 @@ from typing import List, Literal, Union import reflex as rx from reflex.components.component import Component, ComponentNamespace from reflex.components.core.breakpoints import Responsive +from reflex.constants.compiler import MemoizationMode from reflex.event import no_args_event_spec, passthrough_event_spec from reflex.vars.base import Var @@ -69,6 +70,8 @@ class SelectTrigger(RadixThemesComponent): _valid_parents: List[str] = ["SelectRoot"] + _memoization_mode = MemoizationMode(recursive=False) + class SelectContent(RadixThemesComponent): """The component that pops out when the select is open.""" diff --git a/reflex/components/radix/themes/components/tabs.py b/reflex/components/radix/themes/components/tabs.py index adfb32fab..7b5e5f475 100644 --- a/reflex/components/radix/themes/components/tabs.py +++ b/reflex/components/radix/themes/components/tabs.py @@ -7,6 +7,7 @@ from typing import Any, Dict, List, Literal from reflex.components.component import Component, ComponentNamespace from reflex.components.core.breakpoints import Responsive from reflex.components.core.colors import color +from reflex.constants.compiler import MemoizationMode from reflex.event import EventHandler, passthrough_event_spec from reflex.vars.base import Var @@ -95,6 +96,8 @@ class TabsTrigger(RadixThemesComponent): _valid_parents: List[str] = ["TabsList"] + _memoization_mode = MemoizationMode(recursive=False) + @classmethod def create(cls, *children, **props) -> Component: """Create a TabsTrigger component. diff --git a/reflex/components/radix/themes/components/text_area.py b/reflex/components/radix/themes/components/text_area.py index 83fa8a593..caf98eb2d 100644 --- a/reflex/components/radix/themes/components/text_area.py +++ b/reflex/components/radix/themes/components/text_area.py @@ -96,5 +96,17 @@ class TextArea(RadixThemesComponent, elements.Textarea): return DebounceInput.create(super().create(*children, **props)) return super().create(*children, **props) + def add_style(self): + """Add the style to the component. + + Returns: + The style of the component. + """ + added_style: dict[str, dict] = {} + added_style.setdefault("& textarea", {}) + if "padding" in self.style: + added_style["& textarea"]["padding"] = self.style.pop("padding") + return added_style + text_area = TextArea.create diff --git a/reflex/components/radix/themes/components/text_area.pyi b/reflex/components/radix/themes/components/text_area.pyi index f0903ba98..e1e40c936 100644 --- a/reflex/components/radix/themes/components/text_area.pyi +++ b/reflex/components/radix/themes/components/text_area.pyi @@ -268,4 +268,6 @@ class TextArea(RadixThemesComponent, elements.Textarea): """ ... + def add_style(self): ... + text_area = TextArea.create diff --git a/reflex/components/radix/themes/components/text_field.py b/reflex/components/radix/themes/components/text_field.py index c8bdab443..130fb7aed 100644 --- a/reflex/components/radix/themes/components/text_field.py +++ b/reflex/components/radix/themes/components/text_field.py @@ -105,7 +105,7 @@ class TextFieldRoot(elements.Input, RadixThemesComponent): (value_var := Var.create(value))._var_type ): props["value"] = ternary_operation( - (value_var != Var.create(None)) # pyright: ignore [reportGeneralTypeIssues] + (value_var != Var.create(None)) # pyright: ignore [reportArgumentType] & (value_var != Var(_js_expr="undefined")), value, Var.create(""), diff --git a/reflex/components/radix/themes/layout/__init__.pyi b/reflex/components/radix/themes/layout/__init__.pyi index 6712a3068..21fc8d921 100644 --- a/reflex/components/radix/themes/layout/__init__.pyi +++ b/reflex/components/radix/themes/layout/__init__.pyi @@ -9,7 +9,7 @@ from .container import container as container from .flex import flex as flex from .grid import grid as grid from .list import list_item as list_item -from .list import list_ns as list # noqa +from .list import list_ns as list # noqa: F401 from .list import ordered_list as ordered_list from .list import unordered_list as unordered_list from .section import section as section diff --git a/reflex/components/radix/themes/layout/list.py b/reflex/components/radix/themes/layout/list.py index 937342c7b..31aaed2c2 100644 --- a/reflex/components/radix/themes/layout/list.py +++ b/reflex/components/radix/themes/layout/list.py @@ -72,7 +72,7 @@ class BaseList(Component, MarkdownComponentMap): if isinstance(items, Var): children = [foreach(items, ListItem.create)] else: - children = [ListItem.create(item) for item in items] # type: ignore + children = [ListItem.create(item) for item in items] props["direction"] = "column" style = props.setdefault("style", {}) style["list_style_type"] = list_style_type @@ -189,7 +189,7 @@ ordered_list = list_ns.ordered unordered_list = list_ns.unordered -def __getattr__(name): +def __getattr__(name: Any): # special case for when accessing list to avoid shadowing # python's built in list object. if name == "list": diff --git a/reflex/components/radix/themes/layout/stack.py b/reflex/components/radix/themes/layout/stack.py index d11c3488b..f017ff783 100644 --- a/reflex/components/radix/themes/layout/stack.py +++ b/reflex/components/radix/themes/layout/stack.py @@ -49,14 +49,14 @@ class VStack(Stack): """A vertical stack component.""" # The direction of the stack. - direction: Var[LiteralFlexDirection] = "column" # type: ignore + direction: Var[LiteralFlexDirection] = Var.create("column") class HStack(Stack): """A horizontal stack component.""" # The direction of the stack. - direction: Var[LiteralFlexDirection] = "row" # type: ignore + direction: Var[LiteralFlexDirection] = Var.create("row") stack = Stack.create diff --git a/reflex/components/radix/themes/typography/link.py b/reflex/components/radix/themes/typography/link.py index c93102408..09172b108 100644 --- a/reflex/components/radix/themes/typography/link.py +++ b/reflex/components/radix/themes/typography/link.py @@ -60,7 +60,7 @@ class Link(RadixThemesComponent, A, MemoizationLeaf, MarkdownComponentMap): Returns: The import dict. """ - return next_link._get_imports() # type: ignore + return next_link._get_imports() # pyright: ignore [reportReturnType] @classmethod def create(cls, *children, **props) -> Component: diff --git a/reflex/components/radix/themes/typography/text.py b/reflex/components/radix/themes/typography/text.py index 1663ddedf..cb6527915 100644 --- a/reflex/components/radix/themes/typography/text.py +++ b/reflex/components/radix/themes/typography/text.py @@ -47,7 +47,7 @@ class Text(elements.Span, RadixThemesComponent, MarkdownComponentMap): as_child: Var[bool] # Change the default rendered element into a semantically appropriate alternative (cannot be used with asChild) - as_: Var[LiteralType] = "p" # type: ignore + as_: Var[LiteralType] = Var.create("p") # Text size: "1" - "9" size: Var[Responsive[LiteralTextSize]] @@ -71,7 +71,7 @@ class Text(elements.Span, RadixThemesComponent, MarkdownComponentMap): class Span(Text): """A variant of text rendering as element.""" - as_: Var[LiteralType] = "span" # type: ignore + as_: Var[LiteralType] = Var.create("span") class Em(elements.Em, RadixThemesComponent): diff --git a/reflex/components/react_player/react_player.py b/reflex/components/react_player/react_player.py index fb0319ceb..7b7bb34e3 100644 --- a/reflex/components/react_player/react_player.py +++ b/reflex/components/react_player/react_player.py @@ -39,7 +39,7 @@ class ReactPlayer(NoSSRComponent): loop: Var[bool] # Set to true or false to display native player controls. - controls: Var[bool] = True # type: ignore + controls: Var[bool] = Var.create(True) # Set to true to show just the video thumbnail, which loads the full player on click light: Var[bool] diff --git a/reflex/components/recharts/charts.py b/reflex/components/recharts/charts.py index c25107dc0..3e9df4143 100644 --- a/reflex/components/recharts/charts.py +++ b/reflex/components/recharts/charts.py @@ -25,10 +25,10 @@ class ChartBase(RechartsCharts): """A component that wraps a Recharts charts.""" # The width of chart container. String or Integer - width: Var[Union[str, int]] = "100%" # type: ignore + width: Var[Union[str, int]] = Var.create("100%") # The height of chart container. - height: Var[Union[str, int]] = "100%" # type: ignore + height: Var[Union[str, int]] = Var.create("100%") # The customized event handler of click on the component in this chart on_click: EventHandler[no_args_event_spec] @@ -69,7 +69,7 @@ class ChartBase(RechartsCharts): ) @classmethod - def create(cls, *children, **props) -> Component: + def create(cls, *children: Any, **props: Any) -> Component: """Create a chart component. Args: @@ -84,21 +84,19 @@ class ChartBase(RechartsCharts): cls._ensure_valid_dimension("width", width) cls._ensure_valid_dimension("height", height) - dim_props = { - "width": width if width is not None else "100%", - "height": height if height is not None else "100%", - } - # Ensure that the min_height and min_width are set to prevent the chart from collapsing. # We are using small values so that height and width can still be used over min_height and min_width. # Without this, sometimes the chart will not be visible. Causing confusion to the user. # With this, the user will see a small chart and can adjust the height and width and can figure out that the issue is with the size. - dim_props["min_height"] = props.pop("min_height", 10) - dim_props["min_width"] = props.pop("min_width", 10) + min_height = props.pop("min_height", 10) + min_width = props.pop("min_width", 10) return ResponsiveContainer.create( super().create(*children, **props), - **dim_props, # type: ignore + width=width if width is not None else "100%", + height=height if height is not None else "100%", + min_width=min_width, + min_height=min_height, ) @@ -460,10 +458,10 @@ class Treemap(RechartsCharts): alias = "RechartsTreemap" # The width of chart container. String or Integer. Default: "100%" - width: Var[Union[str, int]] = "100%" # type: ignore + width: Var[Union[str, int]] = Var.create("100%") # The height of chart container. String or Integer. Default: "100%" - height: Var[Union[str, int]] = "100%" # type: ignore + height: Var[Union[str, int]] = Var.create("100%") # data of treemap. Array data: Var[List[Dict[str, Any]]] diff --git a/reflex/components/recharts/polar.py b/reflex/components/recharts/polar.py index 7bb623d34..77aa1ef5e 100644 --- a/reflex/components/recharts/polar.py +++ b/reflex/components/recharts/polar.py @@ -64,7 +64,7 @@ class Pie(Recharts): legend_type: Var[LiteralLegendType] # If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. Default: False - label: Var[bool] = False # type: ignore + label: Var[bool] = Var.create(False) # If false set, label lines will not be drawn. If true set, label lines will be drawn which have the props calculated internally. Default: False label_line: Var[bool] diff --git a/reflex/components/sonner/toast.py b/reflex/components/sonner/toast.py index 7177c6473..dbac8e733 100644 --- a/reflex/components/sonner/toast.py +++ b/reflex/components/sonner/toast.py @@ -132,7 +132,7 @@ class ToastProps(PropsBase, NoExtrasAllowedProps): # 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[str, Any]: + def dict(self, *args: Any, **kwargs: Any) -> dict[str, Any]: """Convert the object to a dictionary. Args: @@ -142,7 +142,7 @@ class ToastProps(PropsBase, NoExtrasAllowedProps): Returns: The object as a dictionary with ToastAction fields intact. """ - kwargs.setdefault("exclude_none", True) # type: ignore + 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: @@ -167,7 +167,7 @@ class ToastProps(PropsBase, NoExtrasAllowedProps): class Toaster(Component): """A Toaster Component for displaying toast notifications.""" - library: str = "sonner@1.7.2" + library: str | None = "sonner@1.7.2" tag = "Toaster" @@ -222,6 +222,8 @@ class Toaster(Component): Returns: The hooks for the toaster component. """ + if self.library is None: + return [] hook = Var( _js_expr=f"{toast_ref} = toast", _var_data=VarData( @@ -266,7 +268,7 @@ class Toaster(Component): raise ValueError("Toast message or title or description must be provided.") if props: - args = LiteralVar.create(ToastProps(component_name="rx.toast", **props)) # pyright: ignore [reportCallIssue, reportGeneralTypeIssues] + args = LiteralVar.create(ToastProps(component_name="rx.toast", **props)) # pyright: ignore [reportCallIssue] toast = toast_command.call(message, args) else: toast = toast_command.call(message) @@ -274,12 +276,12 @@ class Toaster(Component): return run_script(toast) @staticmethod - def toast_info(message: str | Var = "", **kwargs): + def toast_info(message: str | Var = "", **kwargs: Any): """Display an info toast message. Args: message: The message to display. - kwargs: Additional toast props. + **kwargs: Additional toast props. Returns: The toast event. @@ -287,12 +289,12 @@ class Toaster(Component): return Toaster.send_toast(message, level="info", **kwargs) @staticmethod - def toast_warning(message: str | Var = "", **kwargs): + def toast_warning(message: str | Var = "", **kwargs: Any): """Display a warning toast message. Args: message: The message to display. - kwargs: Additional toast props. + **kwargs: Additional toast props. Returns: The toast event. @@ -300,12 +302,12 @@ class Toaster(Component): return Toaster.send_toast(message, level="warning", **kwargs) @staticmethod - def toast_error(message: str | Var = "", **kwargs): + def toast_error(message: str | Var = "", **kwargs: Any): """Display an error toast message. Args: message: The message to display. - kwargs: Additional toast props. + **kwargs: Additional toast props. Returns: The toast event. @@ -313,12 +315,12 @@ class Toaster(Component): return Toaster.send_toast(message, level="error", **kwargs) @staticmethod - def toast_success(message: str | Var = "", **kwargs): + def toast_success(message: str | Var = "", **kwargs: Any): """Display a success toast message. Args: message: The message to display. - kwargs: Additional toast props. + **kwargs: Additional toast props. Returns: The toast event. @@ -350,7 +352,7 @@ class Toaster(Component): return run_script(dismiss_action) @classmethod - def create(cls, *children, **props) -> Component: + def create(cls, *children: Any, **props: Any) -> Component: """Create a toaster component. Args: diff --git a/reflex/components/sonner/toast.pyi b/reflex/components/sonner/toast.pyi index 7fd9fdf54..829e959d5 100644 --- a/reflex/components/sonner/toast.pyi +++ b/reflex/components/sonner/toast.pyi @@ -51,7 +51,7 @@ class ToastProps(PropsBase, NoExtrasAllowedProps): on_dismiss: Optional[Any] on_auto_close: Optional[Any] - def dict(self, *args, **kwargs) -> dict[str, Any]: ... + def dict(self, *args: Any, **kwargs: Any) -> dict[str, Any]: ... class Toaster(Component): is_used: ClassVar[bool] = False @@ -62,13 +62,13 @@ class Toaster(Component): message: str | Var = "", level: str | None = None, **props ) -> EventSpec: ... @staticmethod - def toast_info(message: str | Var = "", **kwargs): ... + def toast_info(message: str | Var = "", **kwargs: Any): ... @staticmethod - def toast_warning(message: str | Var = "", **kwargs): ... + def toast_warning(message: str | Var = "", **kwargs: Any): ... @staticmethod - def toast_error(message: str | Var = "", **kwargs): ... + def toast_error(message: str | Var = "", **kwargs: Any): ... @staticmethod - def toast_success(message: str | Var = "", **kwargs): ... + def toast_success(message: str | Var = "", **kwargs: Any): ... @staticmethod def toast_dismiss(id: Var | str | None = None): ... @overload diff --git a/reflex/components/suneditor/editor.py b/reflex/components/suneditor/editor.py index d40f0e9ad..3edf27545 100644 --- a/reflex/components/suneditor/editor.py +++ b/reflex/components/suneditor/editor.py @@ -3,7 +3,7 @@ from __future__ import annotations import enum -from typing import Dict, List, Literal, Optional, Tuple, Union +from typing import Any, Dict, List, Literal, Optional, Tuple, Union from reflex.base import Base from reflex.components.component import Component, NoSSRComponent @@ -115,7 +115,7 @@ class Editor(NoSSRComponent): # Alternatively to a string, a dict of your language can be passed to this prop. # Please refer to the library docs for this. # options: "en" | "da" | "de" | "es" | "fr" | "ja" | "ko" | "pt_br" | - # "ru" | "zh_cn" | "ro" | "pl" | "ckb" | "lv" | "se" | "ua" | "he" | "it" + # "ru" | "zh_cn" | "ro" | "pl" | "ckb" | "lv" | "se" | "ua" | "he" | "it" # default: "en". lang: Var[ Union[ @@ -244,11 +244,13 @@ class Editor(NoSSRComponent): } @classmethod - def create(cls, set_options: Optional[EditorOptions] = None, **props) -> Component: + def create( + cls, set_options: Optional[EditorOptions] = None, **props: Any + ) -> Component: """Create an instance of Editor. No children allowed. Args: - set_options(Optional[EditorOptions]): Configuration object to further configure the instance. + set_options: Configuration object to further configure the instance. **props: Any properties to be passed to the Editor Returns: diff --git a/reflex/components/suneditor/editor.pyi b/reflex/components/suneditor/editor.pyi index b52fd43da..5577220cb 100644 --- a/reflex/components/suneditor/editor.pyi +++ b/reflex/components/suneditor/editor.pyi @@ -171,8 +171,8 @@ class Editor(NoSSRComponent): """Create an instance of Editor. No children allowed. Args: - set_options(Optional[EditorOptions]): Configuration object to further configure the instance. - lang: Language of the editor. Alternatively to a string, a dict of your language can be passed to this prop. Please refer to the library docs for this. options: "en" | "da" | "de" | "es" | "fr" | "ja" | "ko" | "pt_br" | "ru" | "zh_cn" | "ro" | "pl" | "ckb" | "lv" | "se" | "ua" | "he" | "it" default: "en". + set_options: Configuration object to further configure the instance. + lang: Language of the editor. Alternatively to a string, a dict of your language can be passed to this prop. Please refer to the library docs for this. options: "en" | "da" | "de" | "es" | "fr" | "ja" | "ko" | "pt_br" | "ru" | "zh_cn" | "ro" | "pl" | "ckb" | "lv" | "se" | "ua" | "he" | "it" default: "en". name: This is used to set the HTML form name of the editor. This means on HTML form submission, it will be submitted together with contents of the editor by the name provided. default_value: Sets the default value of the editor. This is useful if you don't want the on_change method to be called on render. If you want the on_change method to be called on render please use the set_contents prop width: Sets the width of the editor. px and percentage values are accepted, eg width="100%" or width="500px" default: 100% diff --git a/reflex/components/tags/tag.py b/reflex/components/tags/tag.py index 0587c61ed..983726e56 100644 --- a/reflex/components/tags/tag.py +++ b/reflex/components/tags/tag.py @@ -3,13 +3,33 @@ from __future__ import annotations import dataclasses -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Sequence, Union from reflex.event import EventChain from reflex.utils import format, types from reflex.vars.base import LiteralVar, Var +def render_prop(value: Any) -> Any: + """Render the prop. + + Args: + value: The value to render. + + Returns: + The rendered value. + """ + from reflex.components.component import BaseComponent + + if isinstance(value, BaseComponent): + return value.render() + if isinstance(value, Sequence) and not isinstance(value, str): + return [render_prop(v) for v in value] + if callable(value) and not isinstance(value, Var): + return None + return value + + @dataclasses.dataclass() class Tag: """A React tag.""" @@ -49,7 +69,7 @@ class Tag: """Set the tag's fields. Args: - kwargs: The fields to set. + **kwargs: The fields to set. Returns: The tag with the fields @@ -66,7 +86,9 @@ class Tag: Tuple[str, Any]: The field name and value. """ for field in dataclasses.fields(self): - yield field.name, getattr(self, field.name) + rendered_value = render_prop(getattr(self, field.name)) + if rendered_value is not None: + yield field.name, rendered_value def add_props(self, **kwargs: Optional[Any]) -> Tag: """Add props to the tag. diff --git a/reflex/config.py b/reflex/config.py index 2daf8ef1d..6609067f9 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -390,7 +390,7 @@ class EnvVar(Generic[T]): os.environ[self.name] = str(value) -class env_var: # type: ignore # noqa: N801 +class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration] """Descriptor for environment variables.""" name: str @@ -407,7 +407,7 @@ class env_var: # type: ignore # noqa: N801 self.default = default self.internal = internal - def __set_name__(self, owner, name): + def __set_name__(self, owner: Any, name: str): """Set the name of the descriptor. Args: @@ -416,7 +416,7 @@ class env_var: # type: ignore # noqa: N801 """ self.name = name - def __get__(self, instance, owner): + def __get__(self, instance: Any, owner: Any): """Get the EnvVar instance. Args: @@ -435,7 +435,7 @@ class env_var: # type: ignore # noqa: N801 if TYPE_CHECKING: - def env_var(default, internal=False) -> EnvVar: + def env_var(default: Any, internal: bool = False) -> EnvVar: """Typing helper for the env_var descriptor. Args: @@ -490,6 +490,9 @@ class EnvironmentVariables: # The working directory for the next.js commands. REFLEX_WEB_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.WEB)) + # The working directory for the states directory. + REFLEX_STATES_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.STATES)) + # Path to the alembic config file ALEMBIC_CONFIG: EnvVar[ExistingPath] = env_var(Path(constants.ALEMBIC_CONFIG)) @@ -597,7 +600,7 @@ class Config(Base): See the [configuration](https://reflex.dev/docs/getting-started/configuration/) docs for more info. """ - class Config: + class Config: # pyright: ignore [reportIncompatibleVariableOverride] """Pydantic config for the config.""" validate_assignment = True @@ -700,6 +703,9 @@ class Config(Base): # Path to file containing key-values pairs to override in the environment; Dotenv format. env_file: Optional[str] = None + # Whether the app is running in the reflex cloud environment. + is_reflex_cloud: bool = False + def __init__(self, *args, **kwargs): """Initialize the config values. @@ -763,7 +769,7 @@ class Config(Base): """ if self.env_file: try: - from dotenv import load_dotenv # type: ignore + from dotenv import load_dotenv # pyright: ignore [reportMissingImports] # load env file if exists load_dotenv(self.env_file, override=True) diff --git a/reflex/constants/base.py b/reflex/constants/base.py index f737858c0..7fbcdf18a 100644 --- a/reflex/constants/base.py +++ b/reflex/constants/base.py @@ -52,7 +52,7 @@ class Dirs(SimpleNamespace): # The name of the postcss config file. POSTCSS_JS = "postcss.config.js" # The name of the states directory. - STATES = "states" + STATES = ".states" class Reflex(SimpleNamespace): @@ -75,6 +75,8 @@ class Reflex(SimpleNamespace): # If user sets REFLEX_DIR envroment variable use that instead. DIR = PlatformDirs(MODULE_NAME, False).user_data_path + LOGS_DIR = DIR / "logs" + # The root directory of the reflex library. ROOT_DIR = Path(__file__).parents[2] diff --git a/reflex/constants/compiler.py b/reflex/constants/compiler.py index dc5d80fe0..9bc9978dc 100644 --- a/reflex/constants/compiler.py +++ b/reflex/constants/compiler.py @@ -1,10 +1,10 @@ """Compiler variables.""" +import dataclasses import enum from enum import Enum from types import SimpleNamespace -from reflex.base import Base from reflex.constants import Dirs from reflex.utils.imports import ImportVar @@ -151,7 +151,8 @@ class MemoizationDisposition(enum.Enum): NEVER = "never" -class MemoizationMode(Base): +@dataclasses.dataclass(frozen=True) +class MemoizationMode: """The mode for memoizing a Component.""" # The conditions under which the component should be memoized. diff --git a/reflex/constants/config.py b/reflex/constants/config.py index 7425fd864..a49216c00 100644 --- a/reflex/constants/config.py +++ b/reflex/constants/config.py @@ -39,7 +39,14 @@ class GitIgnore(SimpleNamespace): # The gitignore file. FILE = Path(".gitignore") # Files to gitignore. - DEFAULTS = {Dirs.WEB, "*.db", "__pycache__/", "*.py[cod]", "assets/external/"} + DEFAULTS = { + Dirs.WEB, + Dirs.STATES, + "*.db", + "__pycache__/", + "*.py[cod]", + "assets/external/", + } class RequirementsTxt(SimpleNamespace): diff --git a/reflex/custom_components/custom_components.py b/reflex/custom_components/custom_components.py index 8000e7f4c..024e77eee 100644 --- a/reflex/custom_components/custom_components.py +++ b/reflex/custom_components/custom_components.py @@ -83,7 +83,7 @@ def _get_package_config(exit_on_fail: bool = True) -> dict: The package configuration. Raises: - Exit: If the pyproject.toml file is not found. + Exit: If the pyproject.toml file is not found and exit_on_fail is True. """ pyproject = Path(CustomComponents.PYPROJECT_TOML) try: @@ -925,7 +925,7 @@ def _get_file_from_prompt_in_loop() -> Tuple[bytes, str] | None: image_file = file_extension = None while image_file is None: image_filepath = Path( - console.ask("Upload a preview image of your demo app (enter to skip)") + console.ask("Upload a preview image of your demo app (enter to skip)") # pyright: ignore [reportArgumentType] ) if not image_filepath: break diff --git a/reflex/event.py b/reflex/event.py index 6ec1ea4d8..01f9a1008 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -4,7 +4,6 @@ from __future__ import annotations import dataclasses import inspect -import sys import types import urllib.parse from base64 import b64encode @@ -39,6 +38,7 @@ from typing_extensions import ( ) from reflex import constants +from reflex.constants.compiler import CompileVars, Hooks, Imports from reflex.constants.state import FRONTEND_EVENT_STATE from reflex.utils import console, format from reflex.utils.exceptions import ( @@ -245,7 +245,7 @@ class EventHandler(EventActionsMixin): raise EventHandlerTypeError( f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}." ) from e - payload = tuple(zip(fn_args, values)) + payload = tuple(zip(fn_args, values, strict=False)) # Return the event spec. return EventSpec( @@ -265,7 +265,7 @@ class EventSpec(EventActionsMixin): """ # The event handler. - handler: EventHandler = dataclasses.field(default=None) # type: ignore + handler: EventHandler = dataclasses.field(default=None) # pyright: ignore [reportAssignmentType] # The handler on the client to process event. client_handler_name: str = dataclasses.field(default="") @@ -339,7 +339,7 @@ class EventSpec(EventActionsMixin): raise EventHandlerTypeError( f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}." ) from e - new_payload = tuple(zip(fn_args, values)) + new_payload = tuple(zip(fn_args, values, strict=False)) return self.with_args(self.args + new_payload) @@ -542,7 +542,7 @@ class JavasciptKeyboardEvent: shiftKey: bool = False # noqa: N815 -def input_event(e: Var[JavascriptInputEvent]) -> Tuple[Var[str]]: +def input_event(e: ObjectVar[JavascriptInputEvent]) -> Tuple[Var[str]]: """Get the value from an input event. Args: @@ -563,7 +563,9 @@ class KeyInputInfo(TypedDict): shift_key: bool -def key_event(e: Var[JavasciptKeyboardEvent]) -> Tuple[Var[str], Var[KeyInputInfo]]: +def key_event( + e: ObjectVar[JavasciptKeyboardEvent], +) -> Tuple[Var[str], Var[KeyInputInfo]]: """Get the key from a keyboard event. Args: @@ -573,7 +575,7 @@ def key_event(e: Var[JavasciptKeyboardEvent]) -> Tuple[Var[str], Var[KeyInputInf The key from the keyboard event. """ return ( - e.key, + e.key.to(str), Var.create( { "alt_key": e.altKey, @@ -581,7 +583,7 @@ def key_event(e: Var[JavasciptKeyboardEvent]) -> Tuple[Var[str], Var[KeyInputInf "meta_key": e.metaKey, "shift_key": e.shiftKey, }, - ), + ).to(KeyInputInfo), ) @@ -591,7 +593,7 @@ def no_args_event_spec() -> Tuple[()]: Returns: An empty tuple. """ - return () # type: ignore + return () # These chains can be used for their side effects when no other events are desired. @@ -654,9 +656,9 @@ def passthrough_event_spec( # pyright: ignore[reportInconsistentOverload] return values inner_type = tuple(Var[event_type] for event_type in event_types) - return_annotation = Tuple[inner_type] # type: ignore + return_annotation = Tuple[inner_type] - inner.__signature__ = inspect.signature(inner).replace( # type: ignore + inner.__signature__ = inspect.signature(inner).replace( # pyright: ignore [reportFunctionMemberAccess] parameters=[ inspect.Parameter( f"ev_{i}", @@ -738,7 +740,7 @@ class FileUpload: # Call the lambda to get the event chain. events = call_event_fn( on_upload_progress, self.on_upload_progress_args_spec - ) # type: ignore + ) else: raise ValueError(f"{on_upload_progress} is not a valid event handler.") if isinstance(events, Var): @@ -1058,13 +1060,13 @@ def download( is_data_url = (data.js_type() == "string") & ( data.to(str).startswith("data:") - ) # type: ignore + ) # If it's a data: URI, use it as is, otherwise convert the Var to JSON in a data: URI. - url = cond( # type: ignore + url = cond( is_data_url, data.to(str), - "data:text/plain," + data.to_string(), # type: ignore + "data:text/plain," + data.to_string(), ) elif isinstance(data, bytes): # Caller provided bytes, so base64 encode it as a data: URI. @@ -1083,7 +1085,8 @@ def download( ) -def _callback_arg_spec(eval_result): +# This function seems unused. Check if we still need it. If not, remove in 0.7.0 +def _callback_arg_spec(eval_result: Any): """ArgSpec for call_script callback function. Args: @@ -1188,7 +1191,7 @@ def run_script( return call_function(ArgsFunctionOperation.create((), javascript_code), callback) -def get_event(state, event): +def get_event(state: BaseState, event: str): """Get the event from the given state. Args: @@ -1201,7 +1204,7 @@ def get_event(state, event): return f"{state.get_name()}.{event}" -def get_hydrate_event(state) -> str: +def get_hydrate_event(state: BaseState) -> str: """Get the name of the hydrate event for the state. Args: @@ -1238,7 +1241,7 @@ def call_event_handler( #noqa: DAR401 """ - event_spec_args = parse_args_spec(event_spec) # type: ignore + event_spec_args = parse_args_spec(event_spec) if isinstance(event_callback, EventSpec): check_fn_match_arg_spec( @@ -1348,7 +1351,7 @@ def call_event_handler( if delayed_exceptions: raise delayed_exceptions[0] - return event_callback(*event_spec_args) # type: ignore + return event_callback(*event_spec_args) def unwrap_var_annotation(annotation: GenericType): @@ -1360,7 +1363,7 @@ def unwrap_var_annotation(annotation: GenericType): Returns: The unwrapped annotation. """ - if get_origin(annotation) is Var and (args := get_args(annotation)): + if get_origin(annotation) in (Var, ObjectVar) and (args := get_args(annotation)): return args[0] return annotation @@ -1582,7 +1585,7 @@ def fix_events( if not isinstance(e, EventSpec): raise ValueError(f"Unexpected event type, {type(e)}.") name = format.format_event_handler(e.handler) - payload = {k._js_expr: v._decode() for k, v in e.args} # type: ignore + payload = {k._js_expr: v._decode() for k, v in e.args} # Filter router_data to reduce payload size event_router_data = { @@ -1626,12 +1629,12 @@ class EventVar(ObjectVar, python_types=EventSpec): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class LiteralEventVar(VarOperationCall, LiteralVar, EventVar): """A literal event var.""" - _var_value: EventSpec = dataclasses.field(default=None) # type: ignore + _var_value: EventSpec = dataclasses.field(default=None) # pyright: ignore [reportAssignmentType] def __hash__(self) -> int: """Get the hash of the var. @@ -1687,7 +1690,7 @@ class EventChainVar(BuilderFunctionVar, python_types=EventChain): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) # Note: LiteralVar is second in the inheritance list allowing it act like a # CachedVarOperation (ArgsFunctionOperation) and get the _js_expr from the @@ -1695,7 +1698,7 @@ class EventChainVar(BuilderFunctionVar, python_types=EventChain): class LiteralEventChainVar(ArgsFunctionOperationBuilder, LiteralVar, EventChainVar): """A literal event chain var.""" - _var_value: EventChain = dataclasses.field(default=None) # type: ignore + _var_value: EventChain = dataclasses.field(default=None) # pyright: ignore [reportAssignmentType] def __hash__(self) -> int: """Get the hash of the var. @@ -1719,13 +1722,16 @@ class LiteralEventChainVar(ArgsFunctionOperationBuilder, LiteralVar, EventChainV Returns: The created LiteralEventChainVar instance. + + Raises: + ValueError: If the invocation is not a FunctionVar. """ arg_spec = ( value.args_spec[0] if isinstance(value.args_spec, Sequence) else value.args_spec ) - sig = inspect.signature(arg_spec) # type: ignore + sig = inspect.signature(arg_spec) # pyright: ignore [reportArgumentType] if sig.parameters: arg_def = tuple((f"_{p}" for p in sig.parameters)) arg_def_expr = LiteralVar.create([Var(_js_expr=arg) for arg in arg_def]) @@ -1736,10 +1742,21 @@ class LiteralEventChainVar(ArgsFunctionOperationBuilder, LiteralVar, EventChainV arg_def_expr = Var(_js_expr="args") if value.invocation is None: - invocation = FunctionStringVar.create("addEvents") + invocation = FunctionStringVar.create( + CompileVars.ADD_EVENTS, + _var_data=VarData( + imports=Imports.EVENTS, + hooks={Hooks.EVENTS: None}, + ), + ) else: invocation = value.invocation + if invocation is not None and not isinstance(invocation, FunctionVar): + raise ValueError( + f"EventChain invocation must be a FunctionVar, got {invocation!s} of type {invocation._var_type!s}." + ) + return cls( _js_expr="", _var_type=EventChain, @@ -1858,7 +1875,7 @@ class EventCallback(Generic[P, T]): value4: V4 | Var[V4], ) -> EventCallback[Q, T]: ... - def __call__(self, *values) -> EventCallback: # type: ignore + def __call__(self, *values) -> EventCallback: # pyright: ignore [reportInconsistentOverload] """Call the function with the values. Args: @@ -1867,17 +1884,17 @@ class EventCallback(Generic[P, T]): Returns: The function with the values. """ - return self.func(*values) # type: ignore + return self.func(*values) # pyright: ignore [reportCallIssue, reportReturnType] @overload def __get__( - self: EventCallback[P, T], instance: None, owner + self: EventCallback[P, T], instance: None, owner: Any ) -> EventCallback[P, T]: ... @overload - def __get__(self, instance, owner) -> Callable[P, T]: ... + def __get__(self, instance: Any, owner: Any) -> Callable[P, T]: ... - def __get__(self, instance, owner) -> Callable: # type: ignore + def __get__(self, instance: Any, owner: Any) -> Callable: """Get the function with the instance bound to it. Args: @@ -1888,9 +1905,9 @@ class EventCallback(Generic[P, T]): The function with the instance bound to it """ if instance is None: - return self.func # type: ignore + return self.func - return partial(self.func, instance) # type: ignore + return partial(self.func, instance) G = ParamSpec("G") @@ -1941,7 +1958,7 @@ class EventNamespace(types.SimpleNamespace): @staticmethod def __call__( func: None = None, *, background: bool | None = None - ) -> Callable[[Callable[Concatenate[BASE_STATE, P], T]], EventCallback[P, T]]: ... + ) -> Callable[[Callable[Concatenate[BASE_STATE, P], T]], EventCallback[P, T]]: ... # pyright: ignore [reportInvalidTypeVarUse] @overload @staticmethod @@ -1984,7 +2001,7 @@ class EventNamespace(types.SimpleNamespace): "Background task must be async function or generator." ) setattr(func, BACKGROUND_TASK_MARKER, True) - return func # type: ignore + return func # pyright: ignore [reportReturnType] if func is not None: return wrapper(func) diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index ca91905ae..8138c2721 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -4,7 +4,6 @@ from __future__ import annotations import dataclasses import re -import sys from typing import Any, Callable, Union from reflex import constants @@ -49,7 +48,7 @@ def _client_state_ref_dict(var_name: str) -> str: @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class ClientStateVar(Var): """A Var that exists on the client via useState.""" @@ -201,9 +200,7 @@ class ClientStateVar(Var): ) .to(self._var_type) ._replace( - merge_var_data=VarData( # type: ignore - imports=_refs_import if self._global_ref else {} - ) + merge_var_data=VarData(imports=_refs_import if self._global_ref else {}) ) ) diff --git a/reflex/experimental/hooks.py b/reflex/experimental/hooks.py index e0d3b4a56..c00dd3bf9 100644 --- a/reflex/experimental/hooks.py +++ b/reflex/experimental/hooks.py @@ -11,7 +11,7 @@ def _compose_react_imports(tags: list[str]) -> dict[str, list[ImportVar]]: return {"react": [ImportVar(tag=tag) for tag in tags]} -def const(name, value) -> Var: +def const(name: str | list[str], value: str | Var) -> Var: """Create a constant Var. Args: @@ -26,7 +26,7 @@ def const(name, value) -> Var: return Var(_js_expr=f"const {name} = {value}") -def useCallback(func, deps) -> Var: # noqa: N802 +def useCallback(func: str, deps: list) -> Var: # noqa: N802 """Create a useCallback hook with a function and dependencies. Args: @@ -42,7 +42,7 @@ def useCallback(func, deps) -> Var: # noqa: N802 ) -def useContext(context) -> Var: # noqa: N802 +def useContext(context: str) -> Var: # noqa: N802 """Create a useContext hook with a context. Args: @@ -57,7 +57,7 @@ def useContext(context) -> Var: # noqa: N802 ) -def useRef(default) -> Var: # noqa: N802 +def useRef(default: str) -> Var: # noqa: N802 """Create a useRef hook with a default value. Args: @@ -72,7 +72,7 @@ def useRef(default) -> Var: # noqa: N802 ) -def useState(var_name, default=None) -> Var: # noqa: N802 +def useState(var_name: str, default: str | None = None) -> Var: # noqa: N802 """Create a useState hook with a variable name and setter name. Args: diff --git a/reflex/experimental/layout.py b/reflex/experimental/layout.py index e5a1bab04..d54e87f8b 100644 --- a/reflex/experimental/layout.py +++ b/reflex/experimental/layout.py @@ -12,6 +12,7 @@ from reflex.components.radix.themes.components.icon_button import IconButton from reflex.components.radix.themes.layout.box import Box from reflex.components.radix.themes.layout.container import Container from reflex.components.radix.themes.layout.stack import HStack +from reflex.constants.compiler import MemoizationMode from reflex.event import run_script from reflex.experimental import hooks from reflex.state import ComponentState @@ -44,10 +45,10 @@ class Sidebar(Box, MemoizationLeaf): Returns: The style of the component. """ - sidebar: Component = self.children[-2] # type: ignore - spacer: Component = self.children[-1] # type: ignore + sidebar: Component = self.children[-2] # pyright: ignore [reportAssignmentType] + spacer: Component = self.children[-1] # pyright: ignore [reportAssignmentType] open = ( - self.State.open # type: ignore + self.State.open # pyright: ignore [reportAttributeAccessIssue] if self.State else Var(_js_expr="open") ) @@ -146,6 +147,8 @@ sidebar_trigger_style = { class SidebarTrigger(Fragment): """A component that renders the sidebar trigger.""" + _memoization_mode = MemoizationMode(recursive=False) + @classmethod def create(cls, sidebar: Component, **props): """Create the sidebar trigger component. @@ -159,11 +162,11 @@ class SidebarTrigger(Fragment): """ trigger_props = {**props, **sidebar_trigger_style} - inner_sidebar: Component = sidebar.children[0] # type: ignore + inner_sidebar: Component = sidebar.children[0] # pyright: ignore [reportAssignmentType] sidebar_width = inner_sidebar.style.get("width") if sidebar.State: - open, toggle = sidebar.State.open, sidebar.State.toggle # type: ignore + open, toggle = sidebar.State.open, sidebar.State.toggle # pyright: ignore [reportAttributeAccessIssue] else: open, toggle = ( Var(_js_expr="open"), diff --git a/reflex/experimental/misc.py b/reflex/experimental/misc.py index a2a5a0615..986729881 100644 --- a/reflex/experimental/misc.py +++ b/reflex/experimental/misc.py @@ -1,16 +1,16 @@ """Miscellaneous functions for the experimental package.""" import asyncio -from typing import Any +from typing import Any, Callable -async def run_in_thread(func) -> Any: +async def run_in_thread(func: Callable) -> Any: """Run a function in a separate thread. To not block the UI event queue, run_in_thread must be inside inside a rx.event(background=True) decorated method. Args: - func (callable): The non-async function to run. + func: The non-async function to run. Raises: ValueError: If the function is an async function. diff --git a/reflex/istate/wrappers.py b/reflex/istate/wrappers.py index d4e74cf8a..865bd6c63 100644 --- a/reflex/istate/wrappers.py +++ b/reflex/istate/wrappers.py @@ -6,7 +6,7 @@ from reflex.istate.proxy import ReadOnlyStateProxy from reflex.state import _split_substate_key, _substate_key, get_state_manager -async def get_state(token, state_cls: Any | None = None) -> ReadOnlyStateProxy: +async def get_state(token: str, state_cls: Any | None = None) -> ReadOnlyStateProxy: """Get the instance of a state for a token. Args: diff --git a/reflex/model.py b/reflex/model.py index 295159de0..06bb87b02 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -18,6 +18,7 @@ import sqlalchemy import sqlalchemy.exc import sqlalchemy.ext.asyncio import sqlalchemy.orm +from alembic.runtime.migration import MigrationContext from reflex.base import Base from reflex.config import environment, get_config @@ -242,7 +243,7 @@ class ModelRegistry: return metadata -class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssues] +class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssues,reportIncompatibleVariableOverride] """Base class to define a table in the database.""" # The primary key for the table. @@ -261,7 +262,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue super().__init_subclass__() @classmethod - def _dict_recursive(cls, value): + def _dict_recursive(cls, value: Any): """Recursively serialize the relationship object(s). Args: @@ -393,7 +394,11 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue writer = alembic.autogenerate.rewriter.Rewriter() @writer.rewrites(alembic.operations.ops.AddColumnOp) - def render_add_column_with_server_default(context, revision, op): + def render_add_column_with_server_default( + context: MigrationContext, + revision: str | None, + op: Any, + ): # Carry the sqlmodel default as server_default so that newly added # columns get the desired default value in existing rows. if op.column.default is not None and op.column.server_default is None: @@ -402,7 +407,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue ) return op - def run_autogenerate(rev, context): + def run_autogenerate(rev: str, context: MigrationContext): revision_context.run_autogenerate(rev, context) return [] @@ -415,7 +420,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue connection=connection, target_metadata=ModelRegistry.get_metadata(), render_item=cls._alembic_render_item, - process_revision_directives=writer, # type: ignore + process_revision_directives=writer, compare_type=False, render_as_batch=True, # for sqlite compatibility ) @@ -444,7 +449,7 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue """ config, script_directory = cls._alembic_config() - def run_upgrade(rev, context): + def run_upgrade(rev: str, context: MigrationContext): return script_directory._upgrade_revs(to_rev, rev) with alembic.runtime.environment.EnvironmentContext( diff --git a/reflex/page.py b/reflex/page.py index 44ca6ab31..5f118aad1 100644 --- a/reflex/page.py +++ b/reflex/page.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections import defaultdict -from typing import Any, Dict, List +from typing import Any, Callable, Dict, List from reflex.config import get_config from reflex.event import BASE_STATE, EventType @@ -42,7 +42,7 @@ def page( The decorated function. """ - def decorator(render_fn): + def decorator(render_fn: Callable): kwargs = {} if route: kwargs["route"] = route @@ -66,7 +66,7 @@ def page( return decorator -def get_decorated_pages(omit_implicit_routes=True) -> list[dict[str, Any]]: +def get_decorated_pages(omit_implicit_routes: bool = True) -> list[dict[str, Any]]: """Get the decorated pages. Args: diff --git a/reflex/reflex.py b/reflex/reflex.py index 34a4a58a5..d1e565665 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -17,7 +17,7 @@ from reflex.state import reset_disk_state_manager from reflex.utils import console, telemetry # Disable typer+rich integration for help panels -typer.core.rich = None # type: ignore +typer.core.rich = None # pyright: ignore [reportPrivateImportUsage] # Create the app. try: @@ -125,8 +125,8 @@ def _run( env: constants.Env = constants.Env.DEV, frontend: bool = True, backend: bool = True, - frontend_port: str = str(config.frontend_port), - backend_port: str = str(config.backend_port), + frontend_port: int = config.frontend_port, + backend_port: int = config.backend_port, backend_host: str = config.backend_host, loglevel: constants.LogLevel = config.loglevel, ): @@ -160,18 +160,22 @@ def _run( # Find the next available open port if applicable. if frontend: frontend_port = processes.handle_port( - "frontend", frontend_port, str(constants.DefaultPorts.FRONTEND_PORT) + "frontend", + frontend_port, + constants.DefaultPorts.FRONTEND_PORT, ) if backend: backend_port = processes.handle_port( - "backend", backend_port, str(constants.DefaultPorts.BACKEND_PORT) + "backend", + backend_port, + constants.DefaultPorts.BACKEND_PORT, ) # Apply the new ports to the config. - if frontend_port != str(config.frontend_port): + if frontend_port != config.frontend_port: config._set_persistent(frontend_port=frontend_port) - if backend_port != str(config.backend_port): + if backend_port != config.backend_port: config._set_persistent(backend_port=backend_port) # Reload the config to make sure the env vars are persistent. @@ -262,10 +266,10 @@ def run( help="Execute only backend.", envvar=environment.REFLEX_BACKEND_ONLY.name, ), - frontend_port: str = typer.Option( + frontend_port: int = typer.Option( config.frontend_port, help="Specify a different frontend port." ), - backend_port: str = typer.Option( + backend_port: int = typer.Option( config.backend_port, help="Specify a different backend port." ), backend_host: str = typer.Option( @@ -355,7 +359,7 @@ def logout( check_version() - logout(loglevel) # type: ignore + logout(loglevel) # pyright: ignore [reportArgumentType] db_cli = typer.Typer() diff --git a/reflex/route.py b/reflex/route.py index 0b7172824..3f49f66e9 100644 --- a/reflex/route.py +++ b/reflex/route.py @@ -103,7 +103,7 @@ def catchall_prefix(route: str) -> str: return route.replace(pattern, "") if pattern else "" -def replace_brackets_with_keywords(input_string): +def replace_brackets_with_keywords(input_string: str) -> str: """Replace brackets and everything inside it in a string with a keyword. Args: diff --git a/reflex/state.py b/reflex/state.py index ddd4f3f7c..23b131116 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -31,6 +31,7 @@ from typing import ( Optional, Sequence, Set, + SupportsIndex, Tuple, Type, TypeVar, @@ -899,7 +900,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): ] if len(parent_states) >= 2: raise ValueError(f"Only one parent state is allowed {parent_states}.") - return parent_states[0] if len(parent_states) == 1 else None # type: ignore + return parent_states[0] if len(parent_states) == 1 else None @classmethod def get_substates(cls) -> set[Type[BaseState]]: @@ -1058,7 +1059,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): setattr(cls, prop._var_field_name, prop) @classmethod - def _create_event_handler(cls, fn): + def _create_event_handler(cls, fn: Any): """Create an event handler for the given function. Args: @@ -1176,14 +1177,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): cls._check_overwritten_dynamic_args(list(args.keys())) - def argsingle_factory(param): - def inner_func(self) -> str: + def argsingle_factory(param: str): + def inner_func(self: BaseState) -> str: return self.router.page.params.get(param, "") return inner_func - def arglist_factory(param): - def inner_func(self) -> List[str]: + def arglist_factory(param: str): + def inner_func(self: BaseState) -> List[str]: return self.router.page.params.get(param, []) return inner_func @@ -1268,8 +1269,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): fn = _no_chain_background_task(type(self), name, handler.fn) else: fn = functools.partial(handler.fn, self) - fn.__module__ = handler.fn.__module__ # type: ignore - fn.__qualname__ = handler.fn.__qualname__ # type: ignore + fn.__module__ = handler.fn.__module__ + fn.__qualname__ = handler.fn.__qualname__ return fn backend_vars = super().__getattribute__("_backend_vars") @@ -1434,6 +1435,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): for part1, part2 in zip( cls.get_full_name().split("."), other.get_full_name().split("."), + strict=True, ): if part1 != part2: break @@ -1633,11 +1635,13 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): """ # Oopsie case: you didn't give me a Var... so get what you give. if not isinstance(var, Var): - return var # type: ignore + return var + + unset = object() # Fast case: this is a literal var and the value is known. - if hasattr(var, "_var_value"): - return var._var_value + if (var_value := getattr(var, "_var_value", unset)) is not unset: + return var_value # pyright: ignore [reportReturnType] var_data = var._get_all_var_data() if var_data is None or not var_data.state: @@ -2452,7 +2456,7 @@ class OnLoadInternalState(State): self.router.session.client_token, router_data=self.router_data, ), - State.set_is_hydrated(True), # type: ignore + State.set_is_hydrated(True), # pyright: ignore [reportAttributeAccessIssue] ] @@ -2596,7 +2600,9 @@ class StateProxy(wrapt.ObjectProxy): """ def __init__( - self, state_instance, parent_state_proxy: Optional["StateProxy"] = None + self, + state_instance: BaseState, + parent_state_proxy: Optional["StateProxy"] = None, ): """Create a proxy for a state instance. @@ -2739,7 +2745,7 @@ class StateProxy(wrapt.ObjectProxy): # ensure mutations to these containers are blocked unless proxy is _mutable return ImmutableMutableProxy( wrapped=value.__wrapped__, - state=self, # type: ignore + state=self, field_name=value._self_field_name, ) if isinstance(value, functools.partial) and value.args[0] is self.__wrapped__: @@ -2752,7 +2758,7 @@ class StateProxy(wrapt.ObjectProxy): ) if isinstance(value, MethodType) and value.__self__ is self.__wrapped__: # Rebind methods to the proxy instance - value = type(value)(value.__func__, self) # type: ignore + value = type(value)(value.__func__, self) return value def __setattr__(self, name: str, value: Any) -> None: @@ -2952,7 +2958,7 @@ class StateManagerMemory(StateManager): # The dict of mutexes for each client _states_locks: Dict[str, asyncio.Lock] = pydantic.PrivateAttr({}) - class Config: + class Config: # pyright: ignore [reportIncompatibleVariableOverride] """The Pydantic config.""" fields = { @@ -3049,7 +3055,7 @@ def is_serializable(value: Any) -> bool: def reset_disk_state_manager(): """Reset the disk state manager.""" - states_directory = prerequisites.get_web_dir() / constants.Dirs.STATES + states_directory = prerequisites.get_states_dir() if states_directory.exists(): for path in states_directory.iterdir(): path.unlink() @@ -3070,7 +3076,7 @@ class StateManagerDisk(StateManager): # The token expiration time (s). token_expiration: int = pydantic.Field(default_factory=_default_token_expiration) - class Config: + class Config: # pyright: ignore [reportIncompatibleVariableOverride] """The Pydantic config.""" fields = { @@ -3097,7 +3103,7 @@ class StateManagerDisk(StateManager): Returns: The states directory. """ - return prerequisites.get_web_dir() / constants.Dirs.STATES + return prerequisites.get_states_dir() def _purge_expired_states(self): """Purge expired states from the disk.""" @@ -3542,7 +3548,9 @@ class StateManagerRedis(StateManager): @validator("lock_warning_threshold") @classmethod - def validate_lock_warning_threshold(cls, lock_warning_threshold: int, values): + def validate_lock_warning_threshold( + cls, lock_warning_threshold: int, values: dict[str, int] + ): """Validate the lock warning threshold. Args: @@ -3808,10 +3816,10 @@ class MutableProxy(wrapt.ObjectProxy): def _mark_dirty( self, - wrapped=None, - instance=None, - args=(), - kwargs=None, + wrapped: Callable | None = None, + instance: BaseState | None = None, + args: tuple = (), + kwargs: dict | None = None, ) -> Any: """Mark the state as dirty, then call a wrapped function. @@ -3885,7 +3893,9 @@ class MutableProxy(wrapt.ObjectProxy): ) return value - def _wrap_recursive_decorator(self, wrapped, instance, args, kwargs) -> Any: + def _wrap_recursive_decorator( + self, wrapped: Callable, instance: BaseState, args: list, kwargs: dict + ) -> Any: """Wrap a function that returns a possibly mutable value. Intended for use with `FunctionWrapper` from the `wrapt` library. @@ -3944,7 +3954,7 @@ class MutableProxy(wrapt.ObjectProxy): return value - def __getitem__(self, key) -> Any: + def __getitem__(self, key: Any) -> Any: """Get the item on the proxied object and return a proxy if mutable. Args: @@ -3967,7 +3977,7 @@ class MutableProxy(wrapt.ObjectProxy): # Recursively wrap mutable items retrieved through this proxy. yield self._wrap_recursive(value) - def __delattr__(self, name): + def __delattr__(self, name: str): """Delete the attribute on the proxied object and mark state dirty. Args: @@ -3975,7 +3985,7 @@ class MutableProxy(wrapt.ObjectProxy): """ self._mark_dirty(super().__delattr__, args=(name,)) - def __delitem__(self, key): + def __delitem__(self, key: str): """Delete the item on the proxied object and mark state dirty. Args: @@ -3983,7 +3993,7 @@ class MutableProxy(wrapt.ObjectProxy): """ self._mark_dirty(super().__delitem__, args=(key,)) - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: Any): """Set the item on the proxied object and mark state dirty. Args: @@ -3992,7 +4002,7 @@ class MutableProxy(wrapt.ObjectProxy): """ self._mark_dirty(super().__setitem__, args=(key, value)) - def __setattr__(self, name, value): + def __setattr__(self, name: str, value: Any): """Set the attribute on the proxied object and mark state dirty. If the attribute starts with "_self_", then the state is NOT marked @@ -4016,7 +4026,7 @@ class MutableProxy(wrapt.ObjectProxy): """ return copy.copy(self.__wrapped__) - def __deepcopy__(self, memo=None) -> Any: + def __deepcopy__(self, memo: dict[int, Any] | None = None) -> Any: """Return a deepcopy of the proxy. Args: @@ -4027,7 +4037,7 @@ class MutableProxy(wrapt.ObjectProxy): """ return copy.deepcopy(self.__wrapped__, memo=memo) - def __reduce_ex__(self, protocol_version): + def __reduce_ex__(self, protocol_version: SupportsIndex): """Get the state for redis serialization. This method is called by cloudpickle to serialize the object. @@ -4091,10 +4101,10 @@ class ImmutableMutableProxy(MutableProxy): def _mark_dirty( self, - wrapped=None, - instance=None, - args=(), - kwargs=None, + wrapped: Callable | None = None, + instance: BaseState | None = None, + args: tuple = (), + kwargs: dict | None = None, ) -> Any: """Raise an exception when an attempt is made to modify the object. diff --git a/reflex/style.py b/reflex/style.py index 9a4c5f207..1d818ed06 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -77,7 +77,7 @@ def set_color_mode( _var_data=VarData.merge( base_setter._get_all_var_data(), new_color_mode._get_all_var_data() ), - ).to(FunctionVar, EventChain) # type: ignore + ).to(FunctionVar, EventChain) # Var resolves to the current color mode for the app ("light", "dark" or "system") @@ -181,7 +181,9 @@ def convert( var_data = None # Track import/hook data from any Vars in the style dict. out = {} - def update_out_dict(return_value, keys_to_update): + def update_out_dict( + return_value: Var | dict | list | str, keys_to_update: tuple[str, ...] + ): for k in keys_to_update: out[k] = return_value @@ -291,6 +293,26 @@ class Style(dict): ) super().__setitem__(key, value) + def __or__(self, other: Style | dict) -> Style: + """Combine two styles. + + Args: + other: The other style to combine. + + Returns: + The combined style. + """ + other_var_data = None + if not isinstance(other, Style): + other_dict, other_var_data = convert(other) + else: + other_dict, other_var_data = other, other._var_data + + new_style = Style(super().__or__(other_dict)) + if self._var_data or other_var_data: + new_style._var_data = VarData.merge(self._var_data, other_var_data) + return new_style + def _format_emotion_style_pseudo_selector(key: str) -> str: """Format a pseudo selector for emotion CSS-in-JS. diff --git a/reflex/testing.py b/reflex/testing.py index d41c2194b..7ade19347 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -78,7 +78,7 @@ T = TypeVar("T") TimeoutType = Optional[Union[int, float]] if platform.system() == "Windows": - FRONTEND_POPEN_ARGS["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore + FRONTEND_POPEN_ARGS["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # pyright: ignore [reportAttributeAccessIssue] FRONTEND_POPEN_ARGS["shell"] = True else: FRONTEND_POPEN_ARGS["start_new_session"] = True @@ -88,7 +88,7 @@ else: class chdir(contextlib.AbstractContextManager): # noqa: N801 """Non thread-safe context manager to change the current working directory.""" - def __init__(self, path): + def __init__(self, path: str | Path): """Prepare contextmanager. Args: @@ -256,7 +256,7 @@ class AppHarness: if self.app_source is not None: app_globals = self._get_globals_from_signature(self.app_source) if isinstance(self.app_source, functools.partial): - self.app_source = self.app_source.func # type: ignore + self.app_source = self.app_source.func # get the source from a function or module object source_code = "\n".join( [ @@ -300,7 +300,9 @@ class AppHarness: raise RuntimeError("App state is not initialized.") self.state_manager = StateManagerRedis.create(self.app_instance._state) else: - self.state_manager = self.app_instance._state_manager + self.state_manager = ( + self.app_instance._state_manager if self.app_instance else None + ) def _reload_state_module(self): """Reload the rx.State module to avoid conflict when reloading.""" @@ -325,7 +327,7 @@ class AppHarness: return _shutdown_redis - def _start_backend(self, port=0): + def _start_backend(self, port: int = 0): if self.app_instance is None or self.app_instance.api is None: raise RuntimeError("App was not initialized.") self.backend = uvicorn.Server( @@ -428,7 +430,7 @@ class AppHarness: return self @staticmethod - def get_app_global_source(key, value): + def get_app_global_source(key: str, value: Any): """Get the source code of a global object. If value is a function or class we render the actual source of value otherwise we assign value to key. @@ -623,23 +625,23 @@ class AppHarness: want_headless = True if driver_clz is None: requested_driver = environment.APP_HARNESS_DRIVER.get() - driver_clz = getattr(webdriver, requested_driver) + driver_clz = getattr(webdriver, requested_driver) # pyright: ignore [reportPossiblyUnboundVariable] if driver_options is None: - driver_options = getattr(webdriver, f"{requested_driver}Options")() - if driver_clz is webdriver.Chrome: + driver_options = getattr(webdriver, f"{requested_driver}Options")() # pyright: ignore [reportPossiblyUnboundVariable] + if driver_clz is webdriver.Chrome: # pyright: ignore [reportPossiblyUnboundVariable] if driver_options is None: - driver_options = webdriver.ChromeOptions() + driver_options = webdriver.ChromeOptions() # pyright: ignore [reportPossiblyUnboundVariable] driver_options.add_argument("--class=AppHarness") if want_headless: driver_options.add_argument("--headless=new") - elif driver_clz is webdriver.Firefox: + elif driver_clz is webdriver.Firefox: # pyright: ignore [reportPossiblyUnboundVariable] if driver_options is None: - driver_options = webdriver.FirefoxOptions() + driver_options = webdriver.FirefoxOptions() # pyright: ignore [reportPossiblyUnboundVariable] if want_headless: driver_options.add_argument("-headless") - elif driver_clz is webdriver.Edge: + elif driver_clz is webdriver.Edge: # pyright: ignore [reportPossiblyUnboundVariable] if driver_options is None: - driver_options = webdriver.EdgeOptions() + driver_options = webdriver.EdgeOptions() # pyright: ignore [reportPossiblyUnboundVariable] if want_headless: driver_options.add_argument("headless") if driver_options is None: @@ -655,7 +657,7 @@ class AppHarness: driver_options.set_capability(key, value) if driver_kwargs is None: driver_kwargs = {} - driver = driver_clz(options=driver_options, **driver_kwargs) # type: ignore + driver = driver_clz(options=driver_options, **driver_kwargs) # pyright: ignore [reportOptionalCall, reportArgumentType] driver.get(self.frontend_url) self._frontends.append(driver) return driver @@ -887,8 +889,8 @@ class Subdir404TCPServer(socketserver.TCPServer): request, client_address, self, - directory=str(self.root), # type: ignore - error_page_map=self.error_page_map, # type: ignore + directory=str(self.root), # pyright: ignore [reportCallIssue] + error_page_map=self.error_page_map, # pyright: ignore [reportCallIssue] ) diff --git a/reflex/utils/build.py b/reflex/utils/build.py index 9ea941792..9e35ab984 100644 --- a/reflex/utils/build.py +++ b/reflex/utils/build.py @@ -27,7 +27,7 @@ def set_env_json(): ) -def generate_sitemap_config(deploy_url: str, export=False): +def generate_sitemap_config(deploy_url: str, export: bool = False): """Generate the sitemap config file. Args: diff --git a/reflex/utils/compat.py b/reflex/utils/compat.py index e63492a6b..e4fb5eb35 100644 --- a/reflex/utils/compat.py +++ b/reflex/utils/compat.py @@ -2,6 +2,7 @@ import contextlib import sys +from typing import Any async def windows_hot_reload_lifespan_hack(): @@ -50,11 +51,11 @@ def pydantic_v1_patch(): ] originals = {module: sys.modules.get(module) for module in patched_modules} try: - import pydantic.v1 # type: ignore + import pydantic.v1 - sys.modules["pydantic.fields"] = pydantic.v1.fields # type: ignore - sys.modules["pydantic.main"] = pydantic.v1.main # type: ignore - sys.modules["pydantic.errors"] = pydantic.v1.errors # type: ignore + sys.modules["pydantic.fields"] = pydantic.v1.fields # pyright: ignore [reportAttributeAccessIssue] + sys.modules["pydantic.main"] = pydantic.v1.main # pyright: ignore [reportAttributeAccessIssue] + sys.modules["pydantic.errors"] = pydantic.v1.errors # pyright: ignore [reportAttributeAccessIssue] sys.modules["pydantic"] = pydantic.v1 yield except (ImportError, AttributeError): @@ -74,7 +75,7 @@ with pydantic_v1_patch(): import sqlmodel as sqlmodel -def sqlmodel_field_has_primary_key(field) -> bool: +def sqlmodel_field_has_primary_key(field: Any) -> bool: """Determines if a field is a priamary. Args: diff --git a/reflex/utils/console.py b/reflex/utils/console.py index 1dca10dc3..8f136c8f9 100644 --- a/reflex/utils/console.py +++ b/reflex/utils/console.py @@ -278,7 +278,7 @@ def ask( choices: list[str] | None = None, default: str | None = None, show_choices: bool = True, -) -> str: +) -> str | None: """Takes a prompt question and optionally a list of choices and returns the user input. @@ -293,7 +293,7 @@ def ask( """ return Prompt.ask( question, choices=choices, default=default, show_choices=show_choices - ) # type: ignore + ) def progress(): diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index 692c7d64b..05fbb297c 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -75,10 +75,14 @@ class VarAttributeError(ReflexError, AttributeError): """Custom AttributeError for var related errors.""" +class UntypedVarError(ReflexError, TypeError): + """Custom TypeError for untyped var errors.""" + + class UntypedComputedVarError(ReflexError, TypeError): """Custom TypeError for untyped computed var errors.""" - def __init__(self, var_name): + def __init__(self, var_name: str): """Initialize the UntypedComputedVarError. Args: @@ -90,7 +94,7 @@ class UntypedComputedVarError(ReflexError, TypeError): class MissingAnnotationError(ReflexError, TypeError): """Custom TypeError for missing annotations.""" - def __init__(self, var_name): + def __init__(self, var_name: str): """Initialize the MissingAnnotationError. Args: diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 6ea0ffb99..81ff20ee6 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -71,7 +71,9 @@ def notify_backend(): # run_process_and_launch_url is assumed to be used # only to launch the frontend # If this is not the case, might have to change the logic -def run_process_and_launch_url(run_command: list[str], backend_present=True): +def run_process_and_launch_url( + run_command: list[str | None], backend_present: bool = True +): """Run the process and launch the URL. Args: @@ -89,7 +91,7 @@ def run_process_and_launch_url(run_command: list[str], backend_present=True): if process is None: kwargs = {} if constants.IS_WINDOWS and backend_present: - kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore + kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # pyright: ignore [reportAttributeAccessIssue] process = processes.new_process( run_command, cwd=get_web_dir(), @@ -134,7 +136,7 @@ def run_process_and_launch_url(run_command: list[str], backend_present=True): break # while True -def run_frontend(root: Path, port: str, backend_present=True): +def run_frontend(root: Path, port: str, backend_present: bool = True): """Run the frontend. Args: @@ -151,12 +153,12 @@ def run_frontend(root: Path, port: str, backend_present=True): console.rule("[bold green]App Running") os.environ["PORT"] = str(get_config().frontend_port if port is None else port) run_process_and_launch_url( - [prerequisites.get_package_manager(), "run", "dev"], # type: ignore + [prerequisites.get_package_manager(), "run", "dev"], backend_present, ) -def run_frontend_prod(root: Path, port: str, backend_present=True): +def run_frontend_prod(root: Path, port: str, backend_present: bool = True): """Run the frontend. Args: @@ -173,7 +175,7 @@ def run_frontend_prod(root: Path, port: str, backend_present=True): # Run the frontend in production mode. console.rule("[bold green]App Running") run_process_and_launch_url( - [prerequisites.get_package_manager(), "run", "prod"], # type: ignore + [prerequisites.get_package_manager(), "run", "prod"], backend_present, ) @@ -265,7 +267,7 @@ def get_reload_dirs() -> list[Path]: return list(map(Path.absolute, reload_dirs)) -def run_uvicorn_backend(host, port, loglevel: LogLevel): +def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel): """Run the backend in development mode using Uvicorn. Args: @@ -285,7 +287,7 @@ def run_uvicorn_backend(host, port, loglevel: LogLevel): ) -def run_granian_backend(host, port, loglevel: LogLevel): +def run_granian_backend(host: str, port: int, loglevel: LogLevel): """Run the backend in development mode using Granian. Args: @@ -295,9 +297,11 @@ def run_granian_backend(host, port, loglevel: LogLevel): """ console.debug("Using Granian for backend") try: - from granian import Granian # type: ignore - from granian.constants import Interfaces # type: ignore - from granian.log import LogLevels # type: ignore + from granian import Granian # pyright: ignore [reportMissingImports] + from granian.constants import ( # pyright: ignore [reportMissingImports] + Interfaces, + ) + from granian.log import LogLevels # pyright: ignore [reportMissingImports] Granian( target=get_granian_target(), @@ -307,7 +311,7 @@ def run_granian_backend(host, port, loglevel: LogLevel): log_level=LogLevels(loglevel.value), reload=True, reload_paths=get_reload_dirs(), - reload_ignore_dirs=[".web"], + reload_ignore_dirs=[".web", ".states"], ).serve() except ImportError: console.error( @@ -350,7 +354,7 @@ def run_backend_prod( run_uvicorn_backend_prod(host, port, loglevel) -def run_uvicorn_backend_prod(host, port, loglevel): +def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel): """Run the backend in production mode using Uvicorn. Args: @@ -402,7 +406,7 @@ def run_uvicorn_backend_prod(host, port, loglevel): ) -def run_granian_backend_prod(host, port, loglevel): +def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel): """Run the backend in production mode using Granian. Args: @@ -413,7 +417,9 @@ def run_granian_backend_prod(host, port, loglevel): from reflex.utils import processes try: - from granian.constants import Interfaces # type: ignore + from granian.constants import ( # pyright: ignore [reportMissingImports] + Interfaces, + ) command = [ "granian", @@ -467,22 +473,22 @@ def output_system_info(): system = platform.system() + fnm_info = f"[FNM {prerequisites.get_fnm_version()} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]" + if system != "Windows" or ( system == "Windows" and prerequisites.is_windows_bun_supported() ): dependencies.extend( [ - f"[FNM {prerequisites.get_fnm_version()} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]", - f"[Bun {prerequisites.get_bun_version()} (Expected: {constants.Bun.VERSION}) (PATH: {config.bun_path})]", + fnm_info, + f"[Bun {prerequisites.get_bun_version()} (Expected: {constants.Bun.VERSION}) (PATH: {path_ops.get_bun_path()})]", ], ) else: - dependencies.append( - f"[FNM {prerequisites.get_fnm_version()} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]", - ) + dependencies.append(fnm_info) if system == "Linux": - import distro # type: ignore + import distro os_version = distro.name(pretty=True) else: @@ -494,11 +500,11 @@ def output_system_info(): console.debug(f"{dep}") console.debug( - f"Using package installer at: {prerequisites.get_install_package_manager(on_failure_return_none=True)}" # type: ignore + f"Using package installer at: {prerequisites.get_install_package_manager(on_failure_return_none=True)}" ) console.debug( f"Using package executer at: {prerequisites.get_package_manager(on_failure_return_none=True)}" - ) # type: ignore + ) if system != "Windows": console.debug(f"Unzip path: {path_ops.which('unzip')}") diff --git a/reflex/utils/format.py b/reflex/utils/format.py index 28a9ddc74..9a1639fb4 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -192,7 +192,7 @@ def make_default_page_title(app_name: str, route: str) -> str: return to_title_case(title) -def format_route(route: str, format_case=True) -> str: +def format_route(route: str, format_case: bool = True) -> str: """Format the given route. Args: @@ -252,7 +252,7 @@ def format_prop( # For dictionaries, convert any properties to strings. elif isinstance(prop, dict): - prop = serializers.serialize_dict(prop) # type: ignore + prop = serializers.serialize_dict(prop) # pyright: ignore [reportAttributeAccessIssue] else: # Dump the prop as JSON. @@ -408,14 +408,14 @@ def format_queue_events( from reflex.vars import FunctionVar, Var if not events: - return Var("(() => null)").to(FunctionVar, EventChain) # type: ignore + return Var("(() => null)").to(FunctionVar, EventChain) # If no spec is provided, the function will take no arguments. def _default_args_spec(): return [] # Construct the arguments that the function accepts. - sig = inspect.signature(args_spec or _default_args_spec) # type: ignore + sig = inspect.signature(args_spec or _default_args_spec) if sig.parameters: arg_def = ",".join(f"_{p}" for p in sig.parameters) arg_def = f"({arg_def})" @@ -432,7 +432,7 @@ def format_queue_events( 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) # type: ignore + specs = call_event_fn(spec, args_spec or _default_args_spec) # pyright: ignore [reportAssignmentType, reportArgumentType] if isinstance(specs, Var): raise ValueError( f"Invalid event spec: {specs}. Expected a list of EventSpecs." @@ -444,7 +444,7 @@ def format_queue_events( return Var( f"{arg_def} => {{queueEvents([{','.join(payloads)}], {constants.CompileVars.SOCKET}); " f"processEvent({constants.CompileVars.SOCKET})}}", - ).to(FunctionVar, EventChain) # type: ignore + ).to(FunctionVar, EventChain) def format_query_params(router_data: dict[str, Any]) -> dict[str, str]: diff --git a/reflex/utils/imports.py b/reflex/utils/imports.py index bd422ecc0..46e8e7362 100644 --- a/reflex/utils/imports.py +++ b/reflex/utils/imports.py @@ -90,7 +90,7 @@ def collapse_imports( } -@dataclasses.dataclass(order=True, frozen=True) +@dataclasses.dataclass(frozen=True) class ImportVar: """An import var.""" @@ -122,7 +122,7 @@ class ImportVar: """ if self.alias: return ( - self.alias if self.is_default else " as ".join([self.tag, self.alias]) # type: ignore + self.alias if self.is_default else " as ".join([self.tag, self.alias]) # pyright: ignore [reportCallIssue,reportArgumentType] ) else: return self.tag or "" diff --git a/reflex/utils/lazy_loader.py b/reflex/utils/lazy_loader.py index 61e3967e5..eba89532d 100644 --- a/reflex/utils/lazy_loader.py +++ b/reflex/utils/lazy_loader.py @@ -1,11 +1,17 @@ """Module to implement lazy loading in reflex.""" +from __future__ import annotations + import copy import lazy_loader as lazy -def attach(package_name, submodules=None, submod_attrs=None): +def attach( + package_name: str, + submodules: set | None = None, + submod_attrs: dict | None = None, +): """Replaces a package's __getattr__, __dir__, and __all__ attributes using lazy.attach. The lazy loader __getattr__ doesn't support tuples as list values. We needed to add this functionality (tuples) in Reflex to support 'import as _' statements. This function diff --git a/reflex/utils/path_ops.py b/reflex/utils/path_ops.py index b447718d2..edab085ff 100644 --- a/reflex/utils/path_ops.py +++ b/reflex/utils/path_ops.py @@ -9,7 +9,7 @@ import shutil from pathlib import Path from reflex import constants -from reflex.config import environment +from reflex.config import environment, get_config # Shorthand for join. join = os.linesep.join @@ -118,7 +118,7 @@ def ln(src: str | Path, dest: str | Path, overwrite: bool = False) -> bool: return True -def which(program: str | Path) -> str | Path | None: +def which(program: str | Path) -> Path | None: """Find the path to an executable. Args: @@ -127,7 +127,8 @@ def which(program: str | Path) -> str | Path | None: Returns: The path to the executable. """ - return shutil.which(str(program)) + which_result = shutil.which(program) + return Path(which_result) if which_result else None def use_system_node() -> bool: @@ -156,12 +157,12 @@ def get_node_bin_path() -> Path | None: """ bin_path = Path(constants.Node.BIN_PATH) if not bin_path.exists(): - str_path = which("node") - return Path(str_path).parent.resolve() if str_path else None - return bin_path.resolve() + path = which("node") + return path.parent.absolute() if path else None + return bin_path.absolute() -def get_node_path() -> str | None: +def get_node_path() -> Path | None: """Get the node binary path. Returns: @@ -169,9 +170,8 @@ def get_node_path() -> str | None: """ node_path = Path(constants.Node.PATH) if use_system_node() or not node_path.exists(): - system_node_path = which("node") - return str(system_node_path) if system_node_path else None - return str(node_path) + node_path = which("node") + return node_path def get_npm_path() -> Path | None: @@ -182,11 +182,22 @@ def get_npm_path() -> Path | None: """ npm_path = Path(constants.Node.NPM_PATH) if use_system_node() or not npm_path.exists(): - system_npm_path = which("npm") - npm_path = Path(system_npm_path) if system_npm_path else None + npm_path = which("npm") return npm_path.absolute() if npm_path else None +def get_bun_path() -> Path | None: + """Get bun binary path. + + Returns: + The path to the bun binary file. + """ + bun_path = get_config().bun_path + if use_system_bun() or not bun_path.exists(): + bun_path = which("bun") + return bun_path.absolute() if bun_path else None + + def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]): """Update the contents of a json file. @@ -196,6 +207,9 @@ def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]): """ fp = Path(file_path) + # Create the parent directory if it doesn't exist. + fp.parent.mkdir(parents=True, exist_ok=True) + # Create the file if it doesn't exist. fp.touch(exist_ok=True) diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 94d8f8fbd..8330a315c 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -87,6 +87,17 @@ def get_web_dir() -> Path: return environment.REFLEX_WEB_WORKDIR.get() +def get_states_dir() -> Path: + """Get the working directory for the states. + + Can be overridden with REFLEX_STATES_WORKDIR. + + Returns: + The working directory. + """ + return environment.REFLEX_STATES_WORKDIR.get() + + def check_latest_package_version(package_name: str): """Check if the latest version of the package is installed. @@ -165,7 +176,7 @@ def get_node_version() -> version.Version | None: try: result = processes.new_process([node_path, "-v"], run=True) # The output will be in the form "vX.Y.Z", but version.parse() can handle it - return version.parse(result.stdout) # type: ignore + return version.parse(result.stdout) # pyright: ignore [reportArgumentType] except (FileNotFoundError, TypeError): return None @@ -178,7 +189,7 @@ def get_fnm_version() -> version.Version | None: """ try: result = processes.new_process([constants.Fnm.EXE, "--version"], run=True) - return version.parse(result.stdout.split(" ")[1]) # type: ignore + return version.parse(result.stdout.split(" ")[1]) # pyright: ignore [reportOptionalMemberAccess, reportAttributeAccessIssue] except (FileNotFoundError, TypeError): return None except version.InvalidVersion as e: @@ -194,10 +205,13 @@ def get_bun_version() -> version.Version | None: Returns: The version of bun. """ + bun_path = path_ops.get_bun_path() + if bun_path is None: + return None try: # Run the bun -v command and capture the output - result = processes.new_process([str(get_config().bun_path), "-v"], run=True) - return version.parse(result.stdout) # type: ignore + result = processes.new_process([str(bun_path), "-v"], run=True) + return version.parse(str(result.stdout)) # pyright: ignore [reportArgumentType] except FileNotFoundError: return None except version.InvalidVersion as e: @@ -870,7 +884,9 @@ def init_reflex_json(project_hash: int | None): path_ops.update_json_file(get_web_dir() / constants.Reflex.JSON, reflex_json) -def update_next_config(export=False, transpile_packages: Optional[List[str]] = None): +def update_next_config( + export: bool = False, transpile_packages: Optional[List[str]] = None +): """Update Next.js config from Reflex config. Args: @@ -1051,9 +1067,7 @@ def install_bun(): ) # Skip if bun is already installed. - if Path(get_config().bun_path).exists() and get_bun_version() == version.parse( - constants.Bun.VERSION - ): + if get_bun_version() == version.parse(constants.Bun.VERSION): console.debug("Skipping bun installation as it is already installed.") return @@ -1074,8 +1088,7 @@ def install_bun(): show_logs=console.is_debug(), ) else: - unzip_path = path_ops.which("unzip") - if unzip_path is None: + if path_ops.which("unzip") is None: raise SystemPackageMissingError("unzip") # Run the bun install script. @@ -1117,7 +1130,7 @@ def cached_procedure(cache_file: str, payload_fn: Callable[..., str]): The decorated function. """ - def _inner_decorator(func): + def _inner_decorator(func: Callable): def _inner(*args, **kwargs): payload = _read_cached_procedure_file(cache_file) new_payload = payload_fn(*args, **kwargs) @@ -1177,7 +1190,7 @@ def install_frontend_packages(packages: set[str], config: Config): ) processes.run_process_with_fallback( - [install_package_manager, "install"], # type: ignore + [install_package_manager, "install"], fallback=fallback_command, analytics_enabled=True, show_status_message="Installing base frontend packages", @@ -1279,12 +1292,9 @@ def validate_bun(): Raises: Exit: If custom specified bun does not exist or does not meet requirements. """ - # if a custom bun path is provided, make sure its valid - # This is specific to non-FHS OS - bun_path = get_config().bun_path - if path_ops.use_system_bun(): - bun_path = path_ops.which("bun") - if bun_path != constants.Bun.DEFAULT_PATH: + bun_path = path_ops.get_bun_path() + + if bun_path and bun_path.samefile(constants.Bun.DEFAULT_PATH): console.info(f"Using custom Bun path: {bun_path}") bun_version = get_bun_version() if not bun_version: @@ -1302,7 +1312,7 @@ def validate_bun(): raise typer.Exit(1) -def validate_frontend_dependencies(init=True): +def validate_frontend_dependencies(init: bool = True): """Validate frontend dependencies to ensure they meet requirements. Args: @@ -1473,7 +1483,7 @@ def prompt_for_template_options(templates: list[Template]) -> str: ) # Return the template. - return templates[int(template)].name + return templates[int(template)].name # pyright: ignore [reportArgumentType] def fetch_app_templates(version: str) -> dict[str, Template]: @@ -1626,7 +1636,9 @@ def initialize_default_app(app_name: str): initialize_app_directory(app_name) -def validate_and_create_app_using_remote_template(app_name, template, templates): +def validate_and_create_app_using_remote_template( + app_name: str, template: str, templates: dict[str, Template] +): """Validate and create an app using a remote template. Args: @@ -1838,7 +1850,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash: ) render_func_name = defined_funcs[-1] - def replace_content(_match): + def replace_content(_match: re.Match) -> str: return "\n".join( [ resp.text, @@ -1868,7 +1880,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash: main_module_path.write_text(main_module_code) -def format_address_width(address_width) -> int | None: +def format_address_width(address_width: str | None) -> int | None: """Cast address width to an int. Args: diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 575688eda..c92fb7d1a 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -9,18 +9,20 @@ import os import signal import subprocess from concurrent import futures +from pathlib import Path from typing import Callable, Generator, List, Optional, Tuple, Union import psutil import typer from redis.exceptions import RedisError +from rich.progress import Progress from reflex import constants from reflex.config import environment from reflex.utils import console, path_ops, prerequisites -def kill(pid): +def kill(pid: int): """Kill a process. Args: @@ -48,7 +50,7 @@ def get_num_workers() -> int: return (os.cpu_count() or 1) * 2 + 1 -def get_process_on_port(port) -> Optional[psutil.Process]: +def get_process_on_port(port: int) -> Optional[psutil.Process]: """Get the process on the given port. Args: @@ -62,7 +64,7 @@ def get_process_on_port(port) -> Optional[psutil.Process]: psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess ): if importlib.metadata.version("psutil") >= "6.0.0": - conns = proc.net_connections(kind="inet") # type: ignore + conns = proc.net_connections(kind="inet") else: conns = proc.connections(kind="inet") for conn in conns: @@ -71,7 +73,7 @@ def get_process_on_port(port) -> Optional[psutil.Process]: return None -def is_process_on_port(port) -> bool: +def is_process_on_port(port: int) -> bool: """Check if a process is running on the given port. Args: @@ -83,7 +85,7 @@ def is_process_on_port(port) -> bool: return get_process_on_port(port) is not None -def kill_process_on_port(port): +def kill_process_on_port(port: int): """Kill the process on the given port. Args: @@ -91,10 +93,10 @@ def kill_process_on_port(port): """ if get_process_on_port(port) is not None: with contextlib.suppress(psutil.AccessDenied): - get_process_on_port(port).kill() # type: ignore + get_process_on_port(port).kill() # pyright: ignore [reportOptionalMemberAccess] -def change_port(port: str, _type: str) -> str: +def change_port(port: int, _type: str) -> int: """Change the port. Args: @@ -105,7 +107,7 @@ def change_port(port: str, _type: str) -> str: The new port. """ - new_port = str(int(port) + 1) + new_port = port + 1 if is_process_on_port(new_port): return change_port(new_port, _type) console.info( @@ -114,7 +116,7 @@ def change_port(port: str, _type: str) -> str: return new_port -def handle_port(service_name: str, port: str, default_port: str) -> str: +def handle_port(service_name: str, port: int, default_port: int) -> int: """Change port if the specified port is in use and is not explicitly specified as a CLI arg or config arg. otherwise tell the user the port is in use and exit the app. @@ -133,7 +135,7 @@ def handle_port(service_name: str, port: str, default_port: str) -> str: Exit:when the port is in use. """ if is_process_on_port(port): - if int(port) == int(default_port): + if port == int(default_port): return change_port(port, service_name) else: console.error(f"{service_name.capitalize()} port: {port} is already in use") @@ -141,7 +143,12 @@ def handle_port(service_name: str, port: str, default_port: str) -> str: return port -def new_process(args, run: bool = False, show_logs: bool = False, **kwargs): +def new_process( + args: str | list[str] | list[str | None] | list[str | Path | None], + run: bool = False, + show_logs: bool = False, + **kwargs, +): """Wrapper over subprocess.Popen to unify the launch of child processes. Args: @@ -157,7 +164,7 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs): Exit: When attempting to run a command with a None value. """ # Check for invalid command first. - if None in args: + if isinstance(args, list) and None in args: console.error(f"Invalid command: {args}") raise typer.Exit(1) @@ -191,7 +198,7 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs): } console.debug(f"Running command: {args}") fn = subprocess.run if run else subprocess.Popen - return fn(args, **kwargs) + return fn(args, **kwargs) # pyright: ignore [reportCallIssue, reportArgumentType] @contextlib.contextmanager @@ -212,14 +219,14 @@ def run_concurrently_context( return # Convert the functions to tuples. - fns = [fn if isinstance(fn, tuple) else (fn,) for fn in fns] # type: ignore + fns = [fn if isinstance(fn, tuple) else (fn,) for fn in fns] # pyright: ignore [reportAssignmentType] # Run the functions concurrently. executor = None try: executor = futures.ThreadPoolExecutor(max_workers=len(fns)) # Submit the tasks. - tasks = [executor.submit(*fn) for fn in fns] # type: ignore + tasks = [executor.submit(*fn) for fn in fns] # pyright: ignore [reportArgumentType] # Yield control back to the main thread while tasks are running. yield tasks @@ -247,7 +254,7 @@ def run_concurrently(*fns: Union[Callable, Tuple]) -> None: def stream_logs( message: str, process: subprocess.Popen, - progress=None, + progress: Progress | None = None, suppress_errors: bool = False, analytics_enabled: bool = False, ): @@ -375,10 +382,10 @@ def get_command_with_loglevel(command: list[str]) -> list[str]: def run_process_with_fallback( - args, + args: list[str], *, - show_status_message, - fallback=None, + show_status_message: str, + fallback: str | list | None = None, analytics_enabled: bool = False, **kwargs, ): @@ -417,7 +424,7 @@ def run_process_with_fallback( ) -def execute_command_and_return_output(command) -> str | None: +def execute_command_and_return_output(command: str) -> str | None: """Execute a command and return the output. Args: diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index e6f40c2b5..03888a98b 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -83,7 +83,7 @@ DEFAULT_IMPORTS = { } -def _walk_files(path): +def _walk_files(path: str | Path): """Walk all files in a path. This can be replaced with Path.walk() in python3.12. @@ -114,7 +114,9 @@ def _relative_to_pwd(path: Path) -> Path: return path -def _get_type_hint(value, type_hint_globals, is_optional=True) -> str: +def _get_type_hint( + value: Any, type_hint_globals: dict, is_optional: bool = True +) -> str: """Resolve the type hint for value. Args: @@ -369,7 +371,7 @@ def _extract_class_props_as_ast_nodes( # Try to get default from pydantic field definition. default = target_class.__fields__[name].default if isinstance(default, Var): - default = default._decode() # type: ignore + default = default._decode() kwargs.append( ( @@ -385,7 +387,7 @@ def _extract_class_props_as_ast_nodes( return kwargs -def type_to_ast(typ, cls: type) -> ast.AST: +def type_to_ast(typ: Any, cls: type) -> ast.AST: """Converts any type annotation into its AST representation. Handles nested generic types, unions, etc. @@ -442,7 +444,7 @@ def type_to_ast(typ, cls: type) -> ast.AST: ) -def _get_parent_imports(func): +def _get_parent_imports(func: Callable): _imports = {"reflex.vars": ["Var"]} for type_hint in inspect.get_annotations(func).values(): try: @@ -576,7 +578,7 @@ def _generate_component_create_functiondef( arg=trigger, annotation=ast.Subscript( ast.Name("Optional"), - ast.Index( # type: ignore + ast.Index( # pyright: ignore [reportArgumentType] value=ast.Name( id=ast.unparse( figure_out_return_type( @@ -622,7 +624,7 @@ def _generate_component_create_functiondef( definition = ast.FunctionDef( name="create", args=create_args, - body=[ + body=[ # pyright: ignore [reportArgumentType] ast.Expr( value=ast.Constant( value=_generate_docstrings( @@ -730,7 +732,7 @@ def _generate_namespace_call_functiondef( clz = classes[clz_name] if not hasattr(clz.__call__, "__self__"): - return _generate_staticmethod_call_functiondef(node, clz, type_hint_globals) # type: ignore + return _generate_staticmethod_call_functiondef(node, clz, type_hint_globals) # pyright: ignore [reportArgumentType] # Determine which class is wrapped by the namespace __call__ method component_clz = clz.__call__.__self__ @@ -745,7 +747,7 @@ def _generate_namespace_call_functiondef( definition = _generate_component_create_functiondef( node=None, - clz=component_clz, # type: ignore + clz=component_clz, # pyright: ignore [reportArgumentType] type_hint_globals=type_hint_globals, ) definition.name = "__call__" @@ -825,7 +827,7 @@ class StubGenerator(ast.NodeTransformer): The modified Module node. """ self.generic_visit(node) - return self._remove_docstring(node) # type: ignore + return self._remove_docstring(node) # pyright: ignore [reportReturnType] def visit_Import( self, node: ast.Import | ast.ImportFrom @@ -1059,7 +1061,7 @@ class PyiGenerator: pyi_path.write_text(pyi_content) logger.info(f"Wrote {relpath}") - def _get_init_lazy_imports(self, mod, new_tree): + def _get_init_lazy_imports(self, mod: tuple | ModuleType, new_tree: ast.AST): # retrieve the _SUBMODULES and _SUBMOD_ATTRS from an init file if present. sub_mods = getattr(mod, "_SUBMODULES", None) sub_mod_attrs = getattr(mod, "_SUBMOD_ATTRS", None) @@ -1086,7 +1088,7 @@ class PyiGenerator: + ( " # type: ignore" if mod in pyright_ignore_imports - else " # noqa" # ignore ruff formatting here for cases like rx.list. + else " # noqa: F401" # ignore ruff formatting here for cases like rx.list. if isinstance(mod, tuple) else "" ) @@ -1145,7 +1147,7 @@ class PyiGenerator: if pyi_path: self.written_files.append(pyi_path) - def scan_all(self, targets, changed_files: list[Path] | None = None): + def scan_all(self, targets: list, changed_files: list[Path] | None = None): """Scan all targets for class inheriting Component and generate the .pyi files. Args: diff --git a/reflex/utils/registry.py b/reflex/utils/registry.py index d98178c61..47727d659 100644 --- a/reflex/utils/registry.py +++ b/reflex/utils/registry.py @@ -22,15 +22,15 @@ def latency(registry: str) -> int: return 10_000_000 -def average_latency(registry, attempts: int = 3) -> int: +def average_latency(registry: str, attempts: int = 3) -> int: """Get the average latency of a registry. Args: - registry (str): The URL of the registry. - attempts (int): The number of attempts to make. Defaults to 10. + registry: The URL of the registry. + attempts: The number of attempts to make. Defaults to 10. Returns: - int: The average latency of the registry in microseconds. + The average latency of the registry in microseconds. """ return sum(latency(registry) for _ in range(attempts)) // attempts diff --git a/reflex/utils/serializers.py b/reflex/utils/serializers.py index 4bb8dea92..f78438522 100644 --- a/reflex/utils/serializers.py +++ b/reflex/utils/serializers.py @@ -476,7 +476,7 @@ try: base64_image = base64.b64encode(image_bytes).decode("utf-8") try: # Newer method to get the mime type, but does not always work. - mime_type = image.get_format_mimetype() # type: ignore + mime_type = image.get_format_mimetype() # pyright: ignore [reportAttributeAccessIssue] except AttributeError: try: # Fallback method diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index 67ec45b82..ecfd52428 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -162,7 +162,7 @@ def _send_event(event_data: dict) -> bool: return True -def _send(event, telemetry_enabled, **kwargs): +def _send(event: str, telemetry_enabled: bool | None, **kwargs): from reflex.config import get_config # Get the telemetry_enabled from the config if it is not specified. @@ -189,7 +189,7 @@ def send(event: str, telemetry_enabled: bool | None = None, **kwargs): kwargs: Additional data to send with the event. """ - async def async_send(event, telemetry_enabled, **kwargs): + async def async_send(event: str, telemetry_enabled: bool | None, **kwargs): return _send(event, telemetry_enabled, **kwargs) try: diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 3f10fa757..81b052eac 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -26,7 +26,7 @@ from typing import ( Type, TypeVar, Union, - _GenericAlias, # type: ignore + _GenericAlias, # pyright: ignore [reportAttributeAccessIssue] get_args, get_type_hints, ) @@ -42,7 +42,9 @@ from reflex.components.core.breakpoints import Breakpoints try: from pydantic.v1.fields import ModelField except ModuleNotFoundError: - from pydantic.fields import ModelField # type: ignore + from pydantic.fields import ( + ModelField, # pyright: ignore [reportAttributeAccessIssue] + ) from sqlalchemy.ext.associationproxy import AssociationProxyInstance from sqlalchemy.ext.hybrid import hybrid_property @@ -73,13 +75,15 @@ _GenericAliasTypes: list[type] = [_GenericAlias] with contextlib.suppress(ImportError): # For newer versions of Python. - from types import GenericAlias # type: ignore + from types import GenericAlias _GenericAliasTypes.append(GenericAlias) with contextlib.suppress(ImportError): # For older versions of Python. - from typing import _SpecialGenericAlias # type: ignore + from typing import ( + _SpecialGenericAlias, # pyright: ignore [reportAttributeAccessIssue] + ) _GenericAliasTypes.append(_SpecialGenericAlias) @@ -156,7 +160,7 @@ class Unset: @lru_cache() -def get_origin(tp): +def get_origin(tp: Any): """Get the origin of a class. Args: @@ -178,7 +182,7 @@ def is_generic_alias(cls: GenericType) -> bool: Returns: Whether the class is a generic alias. """ - return isinstance(cls, GenericAliasTypes) + return isinstance(cls, GenericAliasTypes) # pyright: ignore [reportArgumentType] def unionize(*args: GenericType) -> GenericType: @@ -191,14 +195,14 @@ def unionize(*args: GenericType) -> GenericType: The unionized types. """ if not args: - return Any + return Any # pyright: ignore [reportReturnType] if len(args) == 1: return args[0] # We are bisecting the args list here to avoid hitting the recursion limit # In Python versions >= 3.11, we can simply do `return Union[*args]` midpoint = len(args) // 2 first_half, second_half = args[:midpoint], args[midpoint:] - return Union[unionize(*first_half), unionize(*second_half)] + return Union[unionize(*first_half), unionize(*second_half)] # pyright: ignore [reportReturnType] def is_none(cls: GenericType) -> bool: @@ -239,7 +243,7 @@ def is_literal(cls: GenericType) -> bool: return get_origin(cls) is Literal -def has_args(cls) -> bool: +def has_args(cls: Type) -> bool: """Check if the class has generic parameters. Args: @@ -354,13 +358,13 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None if type_ is not None: if hasattr(column_type, "item_type"): try: - item_type = column_type.item_type.python_type # type: ignore + item_type = column_type.item_type.python_type # pyright: ignore [reportAttributeAccessIssue] except NotImplementedError: item_type = None if item_type is not None: if type_ in PrimitiveToAnnotation: - type_ = PrimitiveToAnnotation[type_] # type: ignore - type_ = type_[item_type] # type: ignore + type_ = PrimitiveToAnnotation[type_] + type_ = type_[item_type] # pyright: ignore [reportIndexIssue] if column.nullable: type_ = Optional[type_] return type_ @@ -615,7 +619,9 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool: return ( isinstance(obj, tuple) and len(obj) == len(args) - and all(_isinstance(item, arg) for item, arg in zip(obj, args)) + and all( + _isinstance(item, arg) for item, arg in zip(obj, args, strict=True) + ) ) if origin in (dict, Breakpoints): return isinstance(obj, dict) and all( @@ -757,7 +763,7 @@ def check_prop_in_allowed_types(prop: Any, allowed_types: Iterable) -> bool: return type_ in allowed_types -def is_encoded_fstring(value) -> bool: +def is_encoded_fstring(value: Any) -> bool: """Check if a value is an encoded Var f-string. Args: @@ -800,7 +806,7 @@ def validate_literal(key: str, value: Any, expected_type: Type, comp_name: str): ) -def validate_parameter_literals(func): +def validate_parameter_literals(func: Callable): """Decorator to check that the arguments passed to a function correspond to the correct function parameter if it (the parameter) is a literal type. @@ -818,7 +824,7 @@ def validate_parameter_literals(func): annotations = {param[0]: param[1].annotation for param in func_params} # validate args - for param, arg in zip(annotations, args): + for param, arg in zip(annotations, args, strict=False): if annotations[param] is inspect.Parameter.empty: continue validate_literal(param, arg, annotations[param], func.__name__) @@ -973,7 +979,9 @@ def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> boo # It also ignores when the length of the arguments is different return all( typehint_issubclass(provided_arg, accepted_arg) - for provided_arg, accepted_arg in zip(provided_args, accepted_args) + for provided_arg, accepted_arg in zip( + provided_args, accepted_args, strict=False + ) if accepted_arg is not Any and not isinstance(accepted_arg, TypeVar) ) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 4502ec7bd..ce91f368e 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -12,7 +12,6 @@ import json import random import re import string -import sys import warnings from types import CodeType, FunctionType from typing import ( @@ -94,6 +93,8 @@ VALUE = TypeVar("VALUE") INT_OR_FLOAT = TypeVar("INT_OR_FLOAT", int, float) FAKE_VAR_TYPE = TypeVar("FAKE_VAR_TYPE") OTHER_VAR_TYPE = TypeVar("OTHER_VAR_TYPE") +STRING_T = TypeVar("STRING_T", bound=str) +SEQUENCE_TYPE = TypeVar("SEQUENCE_TYPE", bound=Sequence) warnings.filterwarnings("ignore", message="fields may not start with an underscore") @@ -225,7 +226,7 @@ class VarData: state: str = "", field_name: str = "", imports: ImportDict | ParsedImportDict | None = None, - hooks: Mapping[str, VarData | None] | None = None, + hooks: Mapping[str, VarData | None] | Sequence[str] | str | None = None, components: Iterable[BaseComponent] | None = None, deps: list[Var] | None = None, position: Hooks.HookPosition | None = None, @@ -241,6 +242,10 @@ class VarData: deps: Dependencies of the var for useCallback. position: Position of the hook in the component. """ + if isinstance(hooks, str): + hooks = [hooks] + if not isinstance(hooks, dict): + hooks = {hook: None for hook in (hooks or [])} immutable_imports: ImmutableParsedImportDict = tuple( (k, tuple(v)) for k, v in parse_imports(imports or {}).items() ) @@ -254,6 +259,16 @@ class VarData: object.__setattr__(self, "deps", tuple(deps or [])) object.__setattr__(self, "position", position or None) + if hooks and any(hooks.values()): + merged_var_data = VarData.merge(self, *hooks.values()) + if merged_var_data is not None: + object.__setattr__(self, "state", merged_var_data.state) + object.__setattr__(self, "field_name", merged_var_data.field_name) + object.__setattr__(self, "imports", merged_var_data.imports) + object.__setattr__(self, "hooks", merged_var_data.hooks) + object.__setattr__(self, "deps", merged_var_data.deps) + object.__setattr__(self, "position", merged_var_data.position) + def old_school_imports(self) -> ImportDict: """Return the imports as a mutable dict. @@ -526,7 +541,7 @@ class Var(Generic[VAR_TYPE]): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class ToVarOperation(ToOperation, cls): """Base class of converting a var to another var type.""" @@ -537,7 +552,12 @@ class Var(Generic[VAR_TYPE]): _default_var_type: ClassVar[GenericType] = default_type - ToVarOperation.__name__ = f'To{cls.__name__.removesuffix("Var")}Operation' + new_to_var_operation_name = f"To{cls.__name__.removesuffix('Var')}Operation" + ToVarOperation.__qualname__ = ( + ToVarOperation.__qualname__.removesuffix(ToVarOperation.__name__) + + new_to_var_operation_name + ) + ToVarOperation.__name__ = new_to_var_operation_name _var_subclasses.append( VarSubclassEntry( @@ -595,20 +615,30 @@ class Var(Generic[VAR_TYPE]): @overload def _replace( - self, _var_type: Type[OTHER_VAR_TYPE], merge_var_data=None, **kwargs: Any + self, + _var_type: Type[OTHER_VAR_TYPE], + merge_var_data: VarData | None = None, + **kwargs: Any, ) -> Var[OTHER_VAR_TYPE]: ... @overload def _replace( - self, _var_type: GenericType | None = None, merge_var_data=None, **kwargs: Any + self, + _var_type: GenericType | None = None, + merge_var_data: VarData | None = None, + **kwargs: Any, ) -> Self: ... def _replace( - self, _var_type: GenericType | None = None, merge_var_data=None, **kwargs: Any + self, + _var_type: GenericType | None = None, + merge_var_data: VarData | None = None, + **kwargs: Any, ) -> Self | Var: """Make a copy of this Var with updated fields. Args: + _var_type: The new type of the Var. merge_var_data: VarData to merge into the existing VarData. **kwargs: Var fields to update. @@ -642,12 +672,76 @@ class Var(Generic[VAR_TYPE]): return value_with_replaced + @overload + @classmethod + def create( # type: ignore[override] + cls, + value: bool, + _var_data: VarData | None = None, + ) -> BooleanVar: ... + + @overload + @classmethod + def create( # type: ignore[override] + cls, + value: int, + _var_data: VarData | None = None, + ) -> NumberVar[int]: ... + + @overload @classmethod def create( cls, - value: FAKE_VAR_TYPE, + value: float, _var_data: VarData | None = None, - ) -> Var[FAKE_VAR_TYPE]: + ) -> NumberVar[float]: ... + + @overload + @classmethod + def create( # pyright: ignore [reportOverlappingOverload] + cls, + value: STRING_T, + _var_data: VarData | None = None, + ) -> StringVar[STRING_T]: ... + + @overload + @classmethod + def create( + cls, + value: None, + _var_data: VarData | None = None, + ) -> NoneVar: ... + + @overload + @classmethod + def create( + cls, + value: MAPPING_TYPE, + _var_data: VarData | None = None, + ) -> ObjectVar[MAPPING_TYPE]: ... + + @overload + @classmethod + def create( + cls, + value: SEQUENCE_TYPE, + _var_data: VarData | None = None, + ) -> ArrayVar[SEQUENCE_TYPE]: ... + + @overload + @classmethod + def create( + cls, + value: OTHER_VAR_TYPE, + _var_data: VarData | None = None, + ) -> Var[OTHER_VAR_TYPE]: ... + + @classmethod + def create( + cls, + value: OTHER_VAR_TYPE, + _var_data: VarData | None = None, + ) -> Var[OTHER_VAR_TYPE]: """Create a var from a value. Args: @@ -718,8 +812,8 @@ class Var(Generic[VAR_TYPE]): @overload def to( self, - output: type[Mapping], - ) -> ObjectVar[Mapping]: ... + output: type[MAPPING_TYPE], + ) -> ObjectVar[MAPPING_TYPE]: ... @overload def to( @@ -770,10 +864,10 @@ class Var(Generic[VAR_TYPE]): return self.to(var_subclass.var_subclass, output) if fixed_output_type is None: - return get_to_operation(NoneVar).create(self) # type: ignore + return get_to_operation(NoneVar).create(self) # pyright: ignore [reportReturnType] # Handle fixed_output_type being Base or a dataclass. - if can_use_in_object_var(fixed_output_type): + if can_use_in_object_var(output): return self.to(ObjectVar, output) if inspect.isclass(output): @@ -787,7 +881,7 @@ class Var(Generic[VAR_TYPE]): to_operation_return = var_subclass.to_var_subclass.create( value=self, _var_type=new_var_type ) - return to_operation_return # type: ignore + return to_operation_return # pyright: ignore [reportReturnType] # If we can't determine the first argument, we just replace the _var_type. if not issubclass(output, Var) or var_type is None: @@ -829,6 +923,9 @@ class Var(Generic[VAR_TYPE]): self: Var[Dict[VALUE, OTHER_VAR_TYPE]], ) -> ObjectVar[Dict[VALUE, OTHER_VAR_TYPE]]: ... + @overload + def guess_type(self: Var[BASE_TYPE]) -> ObjectVar[BASE_TYPE]: ... + @overload def guess_type(self) -> Self: ... @@ -985,7 +1082,7 @@ class Var(Generic[VAR_TYPE]): return setter - def _var_set_state(self, state: type[BaseState] | str): + def _var_set_state(self, state: type[BaseState] | str) -> Self: """Set the state of the var. Args: @@ -1000,7 +1097,7 @@ class Var(Generic[VAR_TYPE]): else format_state_name(state.get_full_name()) ) - return StateOperation.create( + return StateOperation.create( # pyright: ignore [reportReturnType] formatted_state_name, self, _var_data=VarData.merge( @@ -1179,62 +1276,6 @@ class Var(Generic[VAR_TYPE]): """ return self - def __getattribute__(self, name: str) -> Any: - """Get an attribute of the var. - - Args: - name: The name of the attribute. - - Returns: - The attribute. - """ - if not _VAR_CACHING: - try: - self_dict = object.__getattribute__(self, "__dict__") - for key in self_dict: - if key.startswith("_cached_"): - del self_dict[key] - except Exception: - pass - return super().__getattribute__(name) - - def __getattr__(self, name: str): - """Get an attribute of the var. - - Args: - name: The name of the attribute. - - Returns: - The attribute. - - Raises: - VarAttributeError: If the attribute does not exist. - TypeError: If the var type is Any. - """ - if name.startswith("_"): - return self.__getattribute__(name) - - if name == "contains": - raise TypeError( - f"Var of type {self._var_type} does not support contains check." - ) - if name == "reverse": - raise TypeError("Cannot reverse non-list var.") - - if self._var_type is Any: - raise TypeError( - f"You must provide an annotation for the state var `{self!s}`. Annotation cannot be `{self._var_type}`." - ) - - if name in REPLACED_NAMES: - raise VarAttributeError( - f"Field {name!r} was renamed to {REPLACED_NAMES[name]!r}" - ) - - raise VarAttributeError( - f"The State var has no attribute '{name}' or may have been annotated wrongly.", - ) - def _decode(self) -> Any: """Decode Var as a python value. @@ -1245,7 +1286,7 @@ class Var(Generic[VAR_TYPE]): The decoded value or the Var name. """ if isinstance(self, LiteralVar): - return self._var_value # type: ignore + return self._var_value try: return json.loads(str(self)) except ValueError: @@ -1295,42 +1336,44 @@ class Var(Generic[VAR_TYPE]): """ from .sequence import ArrayVar - if second_endpoint is None: - return ArrayVar.range.call(first_endpoint).guess_type() - if step is None: - return ArrayVar.range.call(first_endpoint, second_endpoint).guess_type() - return ArrayVar.range.call(first_endpoint, second_endpoint, step).guess_type() + return ArrayVar.range(first_endpoint, second_endpoint, step) def __bool__(self) -> bool: """Raise exception if using Var in a boolean context. - Raises: - VarTypeError: when attempting to bool-ify the Var. - """ - raise VarTypeError( - f"Cannot convert Var {str(self)!r} to bool for use with `if`, `and`, `or`, and `not`. " - "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)." - ) + Raises: + VarTypeError: when attempting to bool-ify the Var. - def __iter__(self) -> Any: - """Raise exception if using Var in an iterable context. + # noqa: DAR101 self + """ + raise VarTypeError( + f"Cannot convert Var {str(self)!r} to bool for use with `if`, `and`, `or`, and `not`. " + "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)." + ) - Raises: - VarTypeError: when attempting to iterate over the Var. - """ - raise VarTypeError( - f"Cannot iterate over Var {str(self)!r}. Instead use `rx.foreach`." - ) + def __iter__(self) -> Any: + """Raise exception if using Var in an iterable context. - def __contains__(self, _: Any) -> Var: - """Override the 'in' operator to alert the user that it is not supported. + Raises: + VarTypeError: when attempting to iterate over the Var. - Raises: - VarTypeError: the operation is not supported - """ - raise VarTypeError( - "'in' operator not supported for Var types, use Var.contains() instead." - ) + # noqa: DAR101 self + """ + raise VarTypeError( + f"Cannot iterate over Var {str(self)!r}. Instead use `rx.foreach`." + ) + + def __contains__(self, _: Any) -> Var: + """Override the 'in' operator to alert the user that it is not supported. + + Raises: + VarTypeError: the operation is not supported + + # noqa: DAR101 self + """ + raise VarTypeError( + "'in' operator not supported for Var types, use Var.contains() instead." + ) OUTPUT = TypeVar("OUTPUT", bound=Var) @@ -1399,7 +1442,7 @@ class ToOperation: """ return VarData.merge( self._original._get_all_var_data(), - self._var_data, # type: ignore + self._var_data, ) @classmethod @@ -1420,10 +1463,10 @@ class ToOperation: The ToOperation. """ return cls( - _js_expr="", # type: ignore - _var_data=_var_data, # type: ignore - _var_type=_var_type or cls._default_var_type, # type: ignore - _original=value, # type: ignore + _js_expr="", # pyright: ignore [reportCallIssue] + _var_data=_var_data, # pyright: ignore [reportCallIssue] + _var_type=_var_type or cls._default_var_type, # pyright: ignore [reportCallIssue, reportAttributeAccessIssue] + _original=value, # pyright: ignore [reportCallIssue] ) @@ -1485,7 +1528,7 @@ class LiteralVar(Var): _var_literal_subclasses.append((cls, var_subclass)) @classmethod - def create( + def create( # pyright: ignore [reportArgumentType] cls, value: Any, _var_data: VarData | None = None, @@ -1559,6 +1602,9 @@ class LiteralVar(Var): _var_data=_var_data, ) + if isinstance(value, range): + return ArrayVar.range(value.start, value.stop, value.step) + raise TypeError( f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}." ) @@ -1566,6 +1612,12 @@ class LiteralVar(Var): def __post_init__(self): """Post-initialize the var.""" + @property + def _var_value(self) -> Any: + raise NotImplementedError( + "LiteralVar subclasses must implement the _var_value property." + ) + def json(self) -> str: """Serialize the var to a JSON string. @@ -1916,7 +1968,7 @@ def figure_out_type(value: Any) -> types.GenericType: class cached_property_no_lock(functools.cached_property): # noqa: N801 """A special version of functools.cached_property that does not use a lock.""" - def __init__(self, func): + def __init__(self, func: Callable): """Initialize the cached_property_no_lock. Args: @@ -1949,7 +2001,7 @@ class CachedVarOperation: next_class = parent_classes[parent_classes.index(CachedVarOperation) + 1] - return next_class.__getattr__(self, name) # type: ignore + return next_class.__getattr__(self, name) def _get_all_var_data(self) -> VarData | None: """Get all VarData associated with the Var. @@ -1971,7 +2023,7 @@ class CachedVarOperation: value._get_all_var_data() if isinstance(value, Var) else None for value in ( getattr(self, field.name) - for field in dataclasses.fields(self) # type: ignore + for field in dataclasses.fields(self) # pyright: ignore [reportArgumentType] ) ), self._var_data, @@ -1988,7 +2040,7 @@ class CachedVarOperation: type(self).__name__, *[ getattr(self, field.name) - for field in dataclasses.fields(self) # type: ignore + for field in dataclasses.fields(self) # pyright: ignore [reportArgumentType] if field.name not in ["_js_expr", "_var_data", "_var_type"] ], ) @@ -2024,7 +2076,7 @@ def is_computed_var(obj: Any) -> TypeGuard[ComputedVar]: @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class ComputedVar(Var[RETURN_TYPE]): """A field with computed getters.""" @@ -2049,7 +2101,7 @@ class ComputedVar(Var[RETURN_TYPE]): _fget: Callable[[BaseState], RETURN_TYPE] = dataclasses.field( default_factory=lambda: lambda _: None - ) # type: ignore + ) # pyright: ignore [reportAssignmentType] def __init__( self, @@ -2130,10 +2182,16 @@ class ComputedVar(Var[RETURN_TYPE]): object.__setattr__(self, "_fget", fget) @override - def _replace(self, merge_var_data=None, **kwargs: Any) -> Self: + def _replace( + self, + _var_type: Any = None, + merge_var_data: VarData | None = None, + **kwargs: Any, + ) -> Self: """Replace the attributes of the ComputedVar. Args: + _var_type: ignored in ComputedVar. merge_var_data: VarData to merge into the existing VarData. **kwargs: Var fields to update. @@ -2227,20 +2285,13 @@ class ComputedVar(Var[RETURN_TYPE]): owner: Type, ) -> ArrayVar[Sequence[LIST_INSIDE]]: ... - @overload - def __get__( - self: ComputedVar[set[LIST_INSIDE]], - instance: None, - owner: Type, - ) -> ArrayVar[set[LIST_INSIDE]]: ... - @overload def __get__(self, instance: None, owner: Type) -> ComputedVar[RETURN_TYPE]: ... @overload def __get__(self, instance: BaseState, owner: Type) -> RETURN_TYPE: ... - def __get__(self, instance: BaseState | None, owner): + def __get__(self, instance: BaseState | None, owner: Type): """Get the ComputedVar value. If the value is already cached on the instance, return the cached value. @@ -2326,10 +2377,10 @@ class ComputedVar(Var[RETURN_TYPE]): return set() with contextlib.suppress(AttributeError): # unbox functools.partial - obj = cast(FunctionType, obj.func) # type: ignore + obj = cast(FunctionType, obj.func) # pyright: ignore [reportAttributeAccessIssue] with contextlib.suppress(AttributeError): # unbox EventHandler - obj = cast(FunctionType, obj.fn) # type: ignore + obj = cast(FunctionType, obj.fn) # pyright: ignore [reportAttributeAccessIssue] if self_name is None and isinstance(obj, FunctionType): try: @@ -2379,7 +2430,7 @@ class ComputedVar(Var[RETURN_TYPE]): d.update( self._deps( objclass=objclass, - obj=ref_obj.fget, # type: ignore + obj=ref_obj.fget, # pyright: ignore [reportArgumentType] ) ) elif ( @@ -2403,7 +2454,7 @@ class ComputedVar(Var[RETURN_TYPE]): self_is_top_of_stack = False return d - def mark_dirty(self, instance) -> None: + def mark_dirty(self, instance: BaseState) -> None: """Mark this ComputedVar as dirty. Args: @@ -2421,7 +2472,7 @@ class ComputedVar(Var[RETURN_TYPE]): hints = get_type_hints(self._fget) if "return" in hints: return hints["return"] - return Any + return Any # pyright: ignore [reportReturnType] @property def __class__(self) -> Type: @@ -2462,7 +2513,7 @@ def computed_var( interval: Optional[Union[datetime.timedelta, int]] = None, backend: bool | None = None, **kwargs, -) -> Callable[[Callable[[BASE_STATE], RETURN_TYPE]], ComputedVar[RETURN_TYPE]]: ... +) -> Callable[[Callable[[BASE_STATE], RETURN_TYPE]], ComputedVar[RETURN_TYPE]]: ... # pyright: ignore [reportInvalidTypeVarUse] @overload @@ -2606,7 +2657,7 @@ def var_operation_return( @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class CustomVarOperation(CachedVarOperation, Var[T]): """Base class for custom var operations.""" @@ -2677,7 +2728,7 @@ class NoneVar(Var[None], python_types=type(None)): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class LiteralNoneVar(LiteralVar, NoneVar): """A var representing None.""" @@ -2739,7 +2790,7 @@ def get_to_operation(var_subclass: Type[Var]) -> Type[ToOperation]: @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class StateOperation(CachedVarOperation, Var): """A var operation that accesses a field on an object.""" @@ -2811,7 +2862,7 @@ def get_uuid_string_var() -> Var: unique_uuid_var = get_unique_variable_name() unique_uuid_var_data = VarData( imports={ - f"$/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # type: ignore + f"$/{constants.Dirs.STATE_PATH}": {ImportVar(tag="generateUUID")}, # pyright: ignore [reportArgumentType] "react": "useMemo", }, hooks={f"const {unique_uuid_var} = useMemo(generateUUID, [])": None}, @@ -2890,19 +2941,6 @@ def _extract_var_data(value: Iterable) -> list[VarData | None]: return var_datas -# These names were changed in reflex 0.3.0 -REPLACED_NAMES = { - "full_name": "_var_full_name", - "name": "_js_expr", - "state": "_var_data.state", - "type_": "_var_type", - "is_local": "_var_is_local", - "is_string": "_var_is_string", - "set_state": "_var_set_state", - "deps": "_deps", -} - - dispatchers: Dict[GenericType, Callable[[Var], Var]] = {} @@ -2983,7 +3021,7 @@ def generic_type_to_actual_type_map( # call recursively for nested generic types and merge the results return { k: v - for generic_arg, actual_arg in zip(generic_args, actual_args) + for generic_arg, actual_arg in zip(generic_args, actual_args, strict=True) for k, v in generic_type_to_actual_type_map(generic_arg, actual_arg).items() } @@ -3149,7 +3187,7 @@ MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping) class Field(Generic[FIELD_TYPE]): """Shadow class for Var to allow for type hinting in the IDE.""" - def __set__(self, instance, value: FIELD_TYPE): + def __set__(self, instance: Any, value: FIELD_TYPE): """Set the Var. Args: @@ -3158,44 +3196,44 @@ class Field(Generic[FIELD_TYPE]): """ @overload - def __get__(self: Field[bool], instance: None, owner) -> BooleanVar: ... + def __get__(self: Field[bool], instance: None, owner: Any) -> BooleanVar: ... @overload - def __get__(self: Field[int], instance: None, owner) -> NumberVar[int]: ... + def __get__(self: Field[int], instance: None, owner: Any) -> NumberVar[int]: ... @overload def __get__(self: Field[float], instance: None, owner) -> NumberVar[float]: ... @overload - def __get__(self: Field[str], instance: None, owner) -> StringVar[str]: ... + def __get__(self: Field[str], instance: None, owner: Any) -> StringVar[str]: ... @overload - def __get__(self: Field[None], instance: None, owner) -> NoneVar: ... + def __get__(self: Field[None], instance: None, owner: Any) -> NoneVar: ... @overload def __get__( self: Field[Sequence[V]] | Field[Set[V]] | Field[List[V]], instance: None, - owner, + owner: Any, ) -> ArrayVar[Sequence[V]]: ... @overload def __get__( - self: Field[MAPPING_TYPE], instance: None, owner + self: Field[MAPPING_TYPE], instance: None, owner: Any ) -> ObjectVar[MAPPING_TYPE]: ... @overload def __get__( - self: Field[BASE_TYPE], instance: None, owner + self: Field[BASE_TYPE], instance: None, owner: Any ) -> ObjectVar[BASE_TYPE]: ... @overload - def __get__(self, instance: None, owner) -> Var[FIELD_TYPE]: ... + def __get__(self, instance: None, owner: Any) -> Var[FIELD_TYPE]: ... @overload - def __get__(self, instance, owner) -> FIELD_TYPE: ... + def __get__(self, instance: Any, owner: Any) -> FIELD_TYPE: ... - def __get__(self, instance, owner): # type: ignore + def __get__(self, instance: Any, owner: Any): # pyright: ignore [reportInconsistentOverload] """Get the Var. Args: diff --git a/reflex/vars/datetime.py b/reflex/vars/datetime.py index 740f768e4..444631893 100644 --- a/reflex/vars/datetime.py +++ b/reflex/vars/datetime.py @@ -3,7 +3,6 @@ from __future__ import annotations import dataclasses -import sys from datetime import date, datetime from typing import TypeVar, Union @@ -123,7 +122,7 @@ class DateTimeVar(Var[DATETIME_T], python_types=(datetime, date)): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class LiteralDatetimeVar(LiteralVar, DateTimeVar): """Base class for immutable datetime and date vars.""" diff --git a/reflex/vars/function.py b/reflex/vars/function.py index 1e9773311..39c95dc9c 100644 --- a/reflex/vars/function.py +++ b/reflex/vars/function.py @@ -167,7 +167,7 @@ class FunctionVar( @overload def partial(self, *args: Var | Any) -> FunctionVar: ... - def partial(self, *args: Var | Any) -> FunctionVar: # type: ignore + def partial(self, *args: Var | Any) -> FunctionVar: # pyright: ignore [reportInconsistentOverload] """Partially apply the function with the given arguments. Args: @@ -1193,6 +1193,7 @@ class FunctionVar( @overload def call(self: FunctionVar[NoReturn], *args: Var | Any) -> Var: ... + def call(self, *args: Var | Any) -> Var: # pyright: ignore [reportInconsistentOverload] def call(self, *args: Var | Any) -> Var: # pyright: ignore [reportInconsistentOverload] """Call the function with the given arguments. @@ -1470,6 +1471,7 @@ class FunctionStringVar(FunctionVar[CALLABLE_TYPE]): Args: func: The function to call. + _var_type: The type of the Var. _var_data: Additional hooks and imports associated with the Var. Returns: @@ -1485,7 +1487,7 @@ class FunctionStringVar(FunctionVar[CALLABLE_TYPE]): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class VarOperationCall(Generic[P, R], CachedVarOperation, Var[R]): """Base class for immutable vars that are the result of a function call.""" @@ -1530,6 +1532,7 @@ class VarOperationCall(Generic[P, R], CachedVarOperation, Var[R]): Args: func: The function to call. *args: The arguments to call the function with. + _var_type: The type of the Var. _var_data: Additional hooks and imports associated with the Var. Returns: @@ -1670,7 +1673,7 @@ def figure_partial_type( @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class ArgsFunctionOperation(CachedVarOperation, FunctionVar[CALLABLE_TYPE]): """Base class for immutable function defined via arguments and return expression.""" @@ -1750,7 +1753,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar[CALLABLE_TYPE]): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class ArgsFunctionOperationBuilder( CachedVarOperation, BuilderFunctionVar[CALLABLE_TYPE] diff --git a/reflex/vars/number.py b/reflex/vars/number.py index 8405f37b6..5cdd2ba00 100644 --- a/reflex/vars/number.py +++ b/reflex/vars/number.py @@ -6,7 +6,6 @@ import dataclasses import functools import json import math -import sys from typing import ( TYPE_CHECKING, Any, @@ -41,7 +40,7 @@ from .base import ( var_operation_return, ) -NUMBER_T = TypeVar("NUMBER_T", int, float, Union[int, float], bool) +NUMBER_T = TypeVar("NUMBER_T", int, float, bool) if TYPE_CHECKING: from .function import FunctionVar @@ -147,7 +146,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): """ from .sequence import ArrayVar, LiteralArrayVar - if isinstance(other, (list, tuple, set, ArrayVar)): + if isinstance(other, (list, tuple, ArrayVar)): if isinstance(other, ArrayVar): return other * self return LiteralArrayVar.create(other) * self @@ -174,7 +173,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): """ from .sequence import ArrayVar, LiteralArrayVar - if isinstance(other, (list, tuple, set, ArrayVar)): + if isinstance(other, (list, tuple, ArrayVar)): if isinstance(other, ArrayVar): return other * self return LiteralArrayVar.create(other) * self @@ -901,7 +900,7 @@ def boolean_not_operation(value: Var[bool]): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class LiteralNumberVar(LiteralVar, NumberVar): """Base class for immutable literal number vars.""" @@ -960,7 +959,7 @@ class LiteralNumberVar(LiteralVar, NumberVar): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class LiteralBooleanVar(LiteralVar, BooleanVar): """Base class for immutable literal boolean vars.""" diff --git a/reflex/vars/object.py b/reflex/vars/object.py index 00d14fa8d..77518f0db 100644 --- a/reflex/vars/object.py +++ b/reflex/vars/object.py @@ -3,7 +3,6 @@ from __future__ import annotations import dataclasses -import sys import typing from inspect import isclass from typing import ( @@ -84,9 +83,9 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping): """ fixed_type = get_origin(self._var_type) or self._var_type if not isclass(fixed_type): - return Any + return Any # pyright: ignore [reportReturnType] args = get_args(self._var_type) if issubclass(fixed_type, Mapping) else () - return args[1] if args else Any + return args[1] if args else Any # pyright: ignore [reportReturnType] def keys(self) -> ArrayVar[Sequence[str]]: """Get the keys of the object. @@ -143,6 +142,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping): # NoReturn is used here to catch when key value is Any @overload + def __getitem__( # pyright: ignore [reportOverlappingOverload] def __getitem__( # pyright: ignore [reportOverlappingOverload] self: ObjectVar[Mapping[Any, NoReturn]], key: Var | Any, @@ -177,12 +177,6 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping): key: Var | Any, ) -> ArrayVar[Sequence[ARRAY_INNER_TYPE]]: ... - @overload - def __getitem__( - self: ObjectVar[Mapping[Any, set[ARRAY_INNER_TYPE]]], - key: Var | Any, - ) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ... - @overload def __getitem__( self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]], @@ -233,12 +227,6 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping): name: str, ) -> ArrayVar[Sequence[ARRAY_INNER_TYPE]]: ... - @overload - def __getattr__( - self: ObjectVar[Mapping[Any, set[ARRAY_INNER_TYPE]]], - name: str, - ) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ... - @overload def __getattr__( self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]], @@ -251,7 +239,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping): name: str, ) -> ObjectItemOperation: ... - def __getattr__(self, name) -> Var: + def __getattr__(self, name: str) -> Var: """Get an attribute of the var. Args: @@ -303,7 +291,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar): """Base class for immutable literal object vars.""" @@ -319,7 +307,7 @@ class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar): The type of the keys of the object. """ args_list = typing.get_args(self._var_type) - return args_list[0] if args_list else Any + return args_list[0] if args_list else Any # pyright: ignore [reportReturnType] def _value_type(self) -> GenericType: """Get the type of the values of the object. @@ -328,7 +316,7 @@ class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar): The type of the values of the object. """ args_list = typing.get_args(self._var_type) - return args_list[1] if args_list else Any + return args_list[1] if args_list else Any # pyright: ignore [reportReturnType] @cached_property_no_lock def _cached_var_name(self) -> str: @@ -353,17 +341,20 @@ class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar): Returns: The JSON representation of the object. + + Raises: + TypeError: The keys and values of the object must be literal vars to get the JSON representation """ - return ( - "{" - + ", ".join( - [ - f"{LiteralVar.create(key).json()}:{LiteralVar.create(value).json()}" - for key, value in self._var_value.items() - ] - ) - + "}" - ) + keys_and_values = [] + for key, value in self._var_value.items(): + key = LiteralVar.create(key) + value = LiteralVar.create(value) + if not isinstance(key, LiteralVar) or not isinstance(value, LiteralVar): + raise TypeError( + "The keys and values of the object must be literal vars to get the JSON representation." + ) + keys_and_values.append(f"{key.json()}:{value.json()}") + return "{" + ", ".join(keys_and_values) + "}" def __hash__(self) -> int: """Get the hash of the var. @@ -502,7 +493,7 @@ def object_merge_operation(lhs: Var, rhs: Var): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class ObjectItemOperation(CachedVarOperation, Var): """Operation to get an item from an object.""" diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index 452eb29b6..0d869e856 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -7,7 +7,6 @@ import functools import inspect import json import re -import sys import typing from typing import ( TYPE_CHECKING, @@ -1103,7 +1102,7 @@ ARRAY_VAR_OF_LIST_ELEMENT = TypeAliasType( @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]): """Base class for immutable literal array vars.""" @@ -1156,32 +1155,39 @@ class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]): Returns: The JSON representation of the var. + + Raises: + TypeError: If the array elements are not of type LiteralVar. """ - return ( - "[" - + ", ".join( - [LiteralVar.create(element).json() for element in self._var_value] - ) - + "]" - ) + elements = [] + for element in self._var_value: + element_var = LiteralVar.create(element) + if not isinstance(element_var, LiteralVar): + raise TypeError( + f"Array elements must be of type LiteralVar, not {type(element_var)}" + ) + elements.append(element_var.json()) + + return "[" + ", ".join(elements) + "]" @classmethod def create( cls, - value: ARRAY_VAR_TYPE, - _var_type: Type[ARRAY_VAR_TYPE] | None = None, + value: OTHER_ARRAY_VAR_TYPE, + _var_type: Type[OTHER_ARRAY_VAR_TYPE] | None = None, _var_data: VarData | None = None, - ) -> LiteralArrayVar[ARRAY_VAR_TYPE]: + ) -> LiteralArrayVar[OTHER_ARRAY_VAR_TYPE]: """Create a var from a string value. Args: value: The value to create the var from. + _var_type: The type of the var. _var_data: Additional hooks and imports associated with the Var. Returns: The var. """ - return cls( + return LiteralArrayVar( _js_expr="", _var_type=figure_out_type(value) if _var_type is None else _var_type, _var_data=_var_data, @@ -1458,7 +1464,7 @@ class ColorVar(StringVar[Color], python_types=Color): @dataclasses.dataclass( eq=False, frozen=True, - **{"slots": True} if sys.version_info >= (3, 10) else {}, + slots=True, ) class LiteralColorVar(CachedVarOperation, LiteralVar, ColorVar): """Base class for immutable literal color vars.""" diff --git a/scripts/wait_for_listening_port.py b/scripts/wait_for_listening_port.py index 43581f0bc..4befa00bd 100644 --- a/scripts/wait_for_listening_port.py +++ b/scripts/wait_for_listening_port.py @@ -14,7 +14,7 @@ from typing import Tuple import psutil -def _pid_exists(pid): +def _pid_exists(pid: int): # os.kill(pid, 0) doesn't work on Windows (actually kills the PID) # psutil.pid_exists() doesn't work on Windows (does os.kill underneath) # psutil.pids() seems to return the right thing. Inefficient but doesn't matter - keeps things simple. @@ -23,7 +23,7 @@ def _pid_exists(pid): return pid in psutil.pids() -def _wait_for_port(port, server_pid, timeout) -> Tuple[bool, str]: +def _wait_for_port(port: int, server_pid: int, timeout: float) -> Tuple[bool, str]: start = time.time() print(f"Waiting for up to {timeout} seconds for port {port} to start listening.") # noqa: T201 while True: diff --git a/tests/integration/test_background_task.py b/tests/integration/test_background_task.py index cb8fda019..f312f8122 100644 --- a/tests/integration/test_background_task.py +++ b/tests/integration/test_background_task.py @@ -37,9 +37,9 @@ def BackgroundTask(): self._task_id += 1 for ix in range(int(self.iterations)): if ix % 2 == 0: - yield State.increment_arbitrary(1) # type: ignore + yield State.increment_arbitrary(1) else: - yield State.increment() # type: ignore + yield State.increment() await asyncio.sleep(0.005) @rx.event @@ -125,8 +125,8 @@ def BackgroundTask(): rx.input( id="iterations", placeholder="Iterations", - value=State.iterations.to_string(), # type: ignore - on_change=State.set_iterations, # type: ignore + value=State.iterations.to_string(), # pyright: ignore [reportAttributeAccessIssue] + on_change=State.set_iterations, # pyright: ignore [reportAttributeAccessIssue] ), rx.button( "Delayed Increment", @@ -288,7 +288,7 @@ def test_background_task( assert background_task._poll_for(lambda: counter.text == "620", timeout=40) # all tasks should have exited and cleaned up assert background_task._poll_for( - lambda: not background_task.app_instance._background_tasks # type: ignore + lambda: not background_task.app_instance._background_tasks # pyright: ignore [reportOptionalMemberAccess] ) diff --git a/tests/integration/test_component_state.py b/tests/integration/test_component_state.py index 381c1d23a..654dc7ce9 100644 --- a/tests/integration/test_component_state.py +++ b/tests/integration/test_component_state.py @@ -30,7 +30,7 @@ def ComponentStateApp(): @rx.event def increment(self): self.count += 1 - self._be = self.count # type: ignore + self._be = self.count # pyright: ignore [reportAttributeAccessIssue] @classmethod def get_component(cls, *children, **props): @@ -72,7 +72,7 @@ def ComponentStateApp(): State=_Counter, ) - app = rx.App(_state=rx.State) # noqa + app = rx.App(_state=rx.State) # noqa: F841 @rx.page() def index(): @@ -89,7 +89,7 @@ def ComponentStateApp(): mc_d, rx.button( "Inc A", - on_click=mc_a.State.increment, # type: ignore + on_click=mc_a.State.increment, # pyright: ignore [reportAttributeAccessIssue, reportOptionalMemberAccess] id="inc-a", ), rx.text( diff --git a/tests/integration/test_connection_banner.py b/tests/integration/test_connection_banner.py index c2a912af6..4867cf868 100644 --- a/tests/integration/test_connection_banner.py +++ b/tests/integration/test_connection_banner.py @@ -1,5 +1,6 @@ """Test case for displaying the connection banner when the websocket drops.""" +import functools from typing import Generator import pytest @@ -11,12 +12,19 @@ from reflex.testing import AppHarness, WebDriver from .utils import SessionStorage -def ConnectionBanner(): - """App with a connection banner.""" +def ConnectionBanner(is_reflex_cloud: bool = False): + """App with a connection banner. + + Args: + is_reflex_cloud: The value for config.is_reflex_cloud. + """ import asyncio import reflex as rx + # Simulate reflex cloud deploy + rx.config.get_config().is_reflex_cloud = is_reflex_cloud + class State(rx.State): foo: int = 0 @@ -31,7 +39,7 @@ def ConnectionBanner(): rx.button( "Increment", id="increment", - on_click=State.set_foo(State.foo + 1), # type: ignore + on_click=State.set_foo(State.foo + 1), # pyright: ignore [reportAttributeAccessIssue] ), rx.button("Delay", id="delay", on_click=State.delay), ) @@ -40,19 +48,43 @@ def ConnectionBanner(): app.add_page(index) +@pytest.fixture( + params=[False, True], ids=["reflex_cloud_disabled", "reflex_cloud_enabled"] +) +def simulate_is_reflex_cloud(request) -> bool: + """Fixture to simulate reflex cloud deployment. + + Args: + request: pytest request fixture. + + Returns: + True if reflex cloud is enabled, False otherwise. + """ + return request.param + + @pytest.fixture() -def connection_banner(tmp_path) -> Generator[AppHarness, None, None]: +def connection_banner( + tmp_path, + simulate_is_reflex_cloud: bool, +) -> Generator[AppHarness, None, None]: """Start ConnectionBanner app at tmp_path via AppHarness. Args: tmp_path: pytest tmp_path fixture + simulate_is_reflex_cloud: Whether is_reflex_cloud is set for the app. Yields: running AppHarness instance """ with AppHarness.create( root=tmp_path, - app_source=ConnectionBanner, + app_source=functools.partial( + ConnectionBanner, is_reflex_cloud=simulate_is_reflex_cloud + ), + app_name="connection_banner_reflex_cloud" + if simulate_is_reflex_cloud + else "connection_banner", ) as harness: yield harness @@ -77,6 +109,38 @@ def has_error_modal(driver: WebDriver) -> bool: return True +def has_cloud_banner(driver: WebDriver) -> bool: + """Check if the cloud banner is displayed. + + Args: + driver: Selenium webdriver instance. + + Returns: + True if the banner is displayed, False otherwise. + """ + try: + driver.find_element( + By.XPATH, "//*[ contains(text(), 'You ran out of compute credits.') ]" + ) + except NoSuchElementException: + return False + else: + return True + + +def _assert_token(connection_banner, driver): + """Poll for backend to be up. + + Args: + connection_banner: AppHarness instance. + driver: Selenium webdriver instance. + """ + ss = SessionStorage(driver) + assert connection_banner._poll_for( + lambda: ss.get("token") is not None + ), "token not found" + + @pytest.mark.asyncio async def test_connection_banner(connection_banner: AppHarness): """Test that the connection banner is displayed when the websocket drops. @@ -88,10 +152,7 @@ async def test_connection_banner(connection_banner: AppHarness): assert connection_banner.backend is not None driver = connection_banner.frontend() - ss = SessionStorage(driver) - assert connection_banner._poll_for( - lambda: ss.get("token") is not None - ), "token not found" + _assert_token(connection_banner, driver) assert connection_banner._poll_for(lambda: not has_error_modal(driver)) @@ -132,3 +193,36 @@ async def test_connection_banner(connection_banner: AppHarness): # Count should have incremented after coming back up assert connection_banner.poll_for_value(counter_element, exp_not_equal="1") == "2" + + +@pytest.mark.asyncio +async def test_cloud_banner( + connection_banner: AppHarness, simulate_is_reflex_cloud: bool +): + """Test that the connection banner is displayed when the websocket drops. + + Args: + connection_banner: AppHarness instance. + simulate_is_reflex_cloud: Whether is_reflex_cloud is set for the app. + """ + assert connection_banner.app_instance is not None + assert connection_banner.backend is not None + driver = connection_banner.frontend() + + driver.add_cookie({"name": "backend-enabled", "value": "truly"}) + driver.refresh() + _assert_token(connection_banner, driver) + assert connection_banner._poll_for(lambda: not has_cloud_banner(driver)) + + driver.add_cookie({"name": "backend-enabled", "value": "false"}) + driver.refresh() + if simulate_is_reflex_cloud: + assert connection_banner._poll_for(lambda: has_cloud_banner(driver)) + else: + _assert_token(connection_banner, driver) + assert connection_banner._poll_for(lambda: not has_cloud_banner(driver)) + + driver.delete_cookie("backend-enabled") + driver.refresh() + _assert_token(connection_banner, driver) + assert connection_banner._poll_for(lambda: not has_cloud_banner(driver)) diff --git a/tests/integration/test_deploy_url.py b/tests/integration/test_deploy_url.py index 8b5776260..207f37609 100644 --- a/tests/integration/test_deploy_url.py +++ b/tests/integration/test_deploy_url.py @@ -19,7 +19,7 @@ def DeployUrlSample() -> None: class State(rx.State): @rx.event def goto_self(self): - return rx.redirect(rx.config.get_config().deploy_url) # type: ignore + return rx.redirect(rx.config.get_config().deploy_url) # pyright: ignore [reportArgumentType] def index(): return rx.fragment( diff --git a/tests/integration/test_dynamic_routes.py b/tests/integration/test_dynamic_routes.py index 9032fd84c..40886a601 100644 --- a/tests/integration/test_dynamic_routes.py +++ b/tests/integration/test_dynamic_routes.py @@ -49,7 +49,7 @@ def DynamicRoute(): read_only=True, id="token", ), - rx.input(value=rx.State.page_id, read_only=True, id="page_id"), # type: ignore + rx.input(value=rx.State.page_id, read_only=True, id="page_id"), # pyright: ignore [reportAttributeAccessIssue] rx.input( value=DynamicState.router.page.raw_path, read_only=True, @@ -60,12 +60,12 @@ def DynamicRoute(): rx.link( "next", href="/page/" + DynamicState.next_page, - id="link_page_next", # type: ignore + id="link_page_next", ), rx.link("missing", href="/missing", id="link_missing"), - rx.list( # type: ignore + rx.list( # pyright: ignore [reportAttributeAccessIssue] rx.foreach( - DynamicState.order, # type: ignore + DynamicState.order, # pyright: ignore [reportAttributeAccessIssue] lambda i: rx.list_item(rx.text(i)), ), ), @@ -98,11 +98,11 @@ def DynamicRoute(): rx.data_list.root( rx.data_list.item( rx.data_list.label("rx.State.arg_str (dynamic)"), - rx.data_list.value(rx.State.arg_str, id="state-arg_str"), # type: ignore + rx.data_list.value(rx.State.arg_str, id="state-arg_str"), # pyright: ignore [reportAttributeAccessIssue] ), rx.data_list.item( rx.data_list.label("ArgState.arg_str (dynamic) (inherited)"), - rx.data_list.value(ArgState.arg_str, id="argstate-arg_str"), # type: ignore + rx.data_list.value(ArgState.arg_str, id="argstate-arg_str"), # pyright: ignore [reportAttributeAccessIssue] ), rx.data_list.item( rx.data_list.label("ArgState.arg"), @@ -110,7 +110,7 @@ def DynamicRoute(): ), rx.data_list.item( rx.data_list.label("ArgSubState.arg_str (dynamic) (inherited)"), - rx.data_list.value(ArgSubState.arg_str, id="argsubstate-arg_str"), # type: ignore + rx.data_list.value(ArgSubState.arg_str, id="argsubstate-arg_str"), # pyright: ignore [reportAttributeAccessIssue] ), rx.data_list.item( rx.data_list.label("ArgSubState.arg (inherited)"), @@ -134,15 +134,15 @@ def DynamicRoute(): height="100vh", ) - @rx.page(route="/redirect-page/[page_id]", on_load=DynamicState.on_load_redir) # type: ignore + @rx.page(route="/redirect-page/[page_id]", on_load=DynamicState.on_load_redir) def redirect_page(): return rx.fragment(rx.text("redirecting...")) app = rx.App(_state=rx.State) - app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) # type: ignore - app.add_page(index, route="/static/x", on_load=DynamicState.on_load) # type: ignore + app.add_page(index, route="/page/[page_id]", on_load=DynamicState.on_load) + app.add_page(index, route="/static/x", on_load=DynamicState.on_load) app.add_page(index) - app.add_custom_404_page(on_load=DynamicState.on_load) # type: ignore + app.add_custom_404_page(on_load=DynamicState.on_load) @pytest.fixture(scope="module") diff --git a/tests/integration/test_event_actions.py b/tests/integration/test_event_actions.py index a1608a954..62600bf7a 100644 --- a/tests/integration/test_event_actions.py +++ b/tests/integration/test_event_actions.py @@ -65,16 +65,16 @@ def TestEventAction(): rx.button( "Stop Prop Only", id="btn-stop-prop-only", - on_click=rx.stop_propagation, # type: ignore + on_click=rx.stop_propagation, # pyright: ignore [reportArgumentType] ), rx.button( "Click event", - on_click=EventActionState.on_click("no_event_actions"), # type: ignore + on_click=EventActionState.on_click("no_event_actions"), # pyright: ignore [reportCallIssue] id="btn-click-event", ), rx.button( "Click stop propagation", - on_click=EventActionState.on_click("stop_propagation").stop_propagation, # type: ignore + on_click=EventActionState.on_click("stop_propagation").stop_propagation, # pyright: ignore [reportCallIssue] id="btn-click-stop-propagation", ), rx.button( @@ -90,13 +90,13 @@ def TestEventAction(): rx.link( "Link", href="#", - on_click=EventActionState.on_click("link_no_event_actions"), # type: ignore + on_click=EventActionState.on_click("link_no_event_actions"), # pyright: ignore [reportCallIssue] id="link", ), rx.link( "Link Stop Propagation", href="#", - on_click=EventActionState.on_click( # type: ignore + on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] "link_stop_propagation" ).stop_propagation, id="link-stop-propagation", @@ -104,13 +104,13 @@ def TestEventAction(): rx.link( "Link Prevent Default Only", href="/invalid", - on_click=rx.prevent_default, # type: ignore + on_click=rx.prevent_default, # pyright: ignore [reportArgumentType] id="link-prevent-default-only", ), rx.link( "Link Prevent Default", href="/invalid", - on_click=EventActionState.on_click( # type: ignore + on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] "link_prevent_default" ).prevent_default, id="link-prevent-default", @@ -118,44 +118,44 @@ def TestEventAction(): rx.link( "Link Both", href="/invalid", - on_click=EventActionState.on_click( # type: ignore + on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] "link_both" ).stop_propagation.prevent_default, id="link-stop-propagation-prevent-default", ), EventFiringComponent.create( id="custom-stop-propagation", - on_click=EventActionState.on_click( # type: ignore + on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] "custom-stop-propagation" ).stop_propagation, ), EventFiringComponent.create( id="custom-prevent-default", - on_click=EventActionState.on_click( # type: ignore + on_click=EventActionState.on_click( # pyright: ignore [reportCallIssue] "custom-prevent-default" ).prevent_default, ), rx.button( "Throttle", id="btn-throttle", - on_click=lambda: EventActionState.on_click_throttle.throttle( + on_click=lambda: EventActionState.on_click_throttle.throttle( # pyright: ignore [reportFunctionMemberAccess] 200 ).stop_propagation, ), rx.button( "Debounce", id="btn-debounce", - on_click=EventActionState.on_click_debounce.debounce( + on_click=EventActionState.on_click_debounce.debounce( # pyright: ignore [reportFunctionMemberAccess] 200 ).stop_propagation, ), - rx.list( # type: ignore + rx.list( # pyright: ignore [reportAttributeAccessIssue] rx.foreach( - EventActionState.order, # type: ignore + EventActionState.order, rx.list_item, ), ), - on_click=EventActionState.on_click("outer"), # type: ignore + on_click=EventActionState.on_click("outer"), # pyright: ignore [reportCallIssue] ) app = rx.App(_state=rx.State) diff --git a/tests/integration/test_event_chain.py b/tests/integration/test_event_chain.py index 5a933253a..df571e884 100644 --- a/tests/integration/test_event_chain.py +++ b/tests/integration/test_event_chain.py @@ -43,32 +43,32 @@ def EventChain(): def event_nested_1(self): self.event_order.append("event_nested_1") yield State.event_nested_2 - yield State.event_arg("nested_1") # type: ignore + yield State.event_arg("nested_1") @rx.event def event_nested_2(self): self.event_order.append("event_nested_2") yield State.event_nested_3 yield rx.console_log("event_nested_2") - yield State.event_arg("nested_2") # type: ignore + yield State.event_arg("nested_2") @rx.event def event_nested_3(self): self.event_order.append("event_nested_3") yield State.event_no_args - yield State.event_arg("nested_3") # type: ignore + yield State.event_arg("nested_3") @rx.event def on_load_return_chain(self): self.event_order.append("on_load_return_chain") - return [State.event_arg(1), State.event_arg(2), State.event_arg(3)] # type: ignore + return [State.event_arg(1), State.event_arg(2), State.event_arg(3)] @rx.event def on_load_yield_chain(self): self.event_order.append("on_load_yield_chain") - yield State.event_arg(4) # type: ignore - yield State.event_arg(5) # type: ignore - yield State.event_arg(6) # type: ignore + yield State.event_arg(4) + yield State.event_arg(5) + yield State.event_arg(6) @rx.event def click_return_event(self): @@ -79,28 +79,28 @@ def EventChain(): def click_return_events(self): self.event_order.append("click_return_events") return [ - State.event_arg(7), # type: ignore + State.event_arg(7), rx.console_log("click_return_events"), - State.event_arg(8), # type: ignore - State.event_arg(9), # type: ignore + State.event_arg(8), + State.event_arg(9), ] @rx.event def click_yield_chain(self): self.event_order.append("click_yield_chain:0") - yield State.event_arg(10) # type: ignore + yield State.event_arg(10) self.event_order.append("click_yield_chain:1") yield rx.console_log("click_yield_chain") - yield State.event_arg(11) # type: ignore + yield State.event_arg(11) self.event_order.append("click_yield_chain:2") - yield State.event_arg(12) # type: ignore + yield State.event_arg(12) self.event_order.append("click_yield_chain:3") @rx.event def click_yield_many_events(self): self.event_order.append("click_yield_many_events") for ix in range(MANY_EVENTS): - yield State.event_arg(ix) # type: ignore + yield State.event_arg(ix) yield rx.console_log(f"many_events_{ix}") self.event_order.append("click_yield_many_events_done") @@ -108,7 +108,7 @@ def EventChain(): def click_yield_nested(self): self.event_order.append("click_yield_nested") yield State.event_nested_1 - yield State.event_arg("yield_nested") # type: ignore + yield State.event_arg("yield_nested") @rx.event def redirect_return_chain(self): @@ -123,12 +123,12 @@ def EventChain(): @rx.event def click_return_int_type(self): self.event_order.append("click_return_int_type") - return State.event_arg_repr_type(1) # type: ignore + return State.event_arg_repr_type(1) @rx.event def click_return_dict_type(self): self.event_order.append("click_return_dict_type") - return State.event_arg_repr_type({"a": 1}) # type: ignore + return State.event_arg_repr_type({"a": 1}) @rx.event async def click_yield_interim_value_async(self): @@ -193,12 +193,12 @@ def EventChain(): rx.button( "Click Int Type", id="click_int_type", - on_click=lambda: State.event_arg_repr_type(1), # type: ignore + on_click=lambda: State.event_arg_repr_type(1), ), rx.button( "Click Dict Type", id="click_dict_type", - on_click=lambda: State.event_arg_repr_type({"a": 1}), # type: ignore + on_click=lambda: State.event_arg_repr_type({"a": 1}), ), rx.button( "Return Chain Int Type", @@ -239,7 +239,7 @@ def EventChain(): rx.text( "return", on_mount=State.on_load_return_chain, - on_unmount=lambda: State.event_arg("unmount"), # type: ignore + on_unmount=lambda: State.event_arg("unmount"), ), token_input, rx.button("Unmount", on_click=rx.redirect("/"), id="unmount"), @@ -251,7 +251,7 @@ def EventChain(): "yield", on_mount=[ State.on_load_yield_chain, - lambda: State.event_arg("mount"), # type: ignore + lambda: State.event_arg("mount"), ], on_unmount=State.event_no_args, ), @@ -259,8 +259,8 @@ def EventChain(): rx.button("Unmount", on_click=rx.redirect("/"), id="unmount"), ) - app.add_page(on_load_return_chain, on_load=State.on_load_return_chain) # type: ignore - app.add_page(on_load_yield_chain, on_load=State.on_load_yield_chain) # type: ignore + app.add_page(on_load_return_chain, on_load=State.on_load_return_chain) + app.add_page(on_load_yield_chain, on_load=State.on_load_yield_chain) app.add_page(on_mount_return_chain) app.add_page(on_mount_yield_chain) diff --git a/tests/integration/test_exception_handlers.py b/tests/integration/test_exception_handlers.py index 4d15bbf01..71858b899 100644 --- a/tests/integration/test_exception_handlers.py +++ b/tests/integration/test_exception_handlers.py @@ -51,12 +51,12 @@ def TestApp(): ), rx.button( "induce_backend_error", - on_click=lambda: TestAppState.divide_by_number(0), # type: ignore + on_click=lambda: TestAppState.divide_by_number(0), # pyright: ignore [reportCallIssue] id="induce-backend-error-btn", ), rx.button( "induce_react_error", - on_click=TestAppState.set_react_error(True), # type: ignore + on_click=TestAppState.set_react_error(True), # pyright: ignore [reportAttributeAccessIssue] id="induce-react-error-btn", ), rx.box( diff --git a/tests/integration/test_input.py b/tests/integration/test_input.py index bddeca45f..5f2948feb 100644 --- a/tests/integration/test_input.py +++ b/tests/integration/test_input.py @@ -26,11 +26,11 @@ def FullyControlledInput(): ), rx.input( id="debounce_input_input", - on_change=State.set_text, # type: ignore + on_change=State.set_text, # pyright: ignore [reportAttributeAccessIssue] value=State.text, ), rx.input(value=State.text, id="value_input", is_read_only=True), - rx.input(on_change=State.set_text, id="on_change_input"), # type: ignore + rx.input(on_change=State.set_text, id="on_change_input"), # pyright: ignore [reportAttributeAccessIssue] rx.el.input( value=State.text, id="plain_value_input", diff --git a/tests/integration/test_lifespan.py b/tests/integration/test_lifespan.py index 578d0b664..6b8233292 100644 --- a/tests/integration/test_lifespan.py +++ b/tests/integration/test_lifespan.py @@ -88,7 +88,7 @@ def LifespanApp(): rx.moment( interval=LifespanState.interval, on_change=LifespanState.tick ), - on_click=LifespanState.set_interval( # type: ignore + on_click=LifespanState.set_interval( # pyright: ignore [reportAttributeAccessIssue] rx.cond(LifespanState.interval, 0, 100) ), id="toggle-tick", diff --git a/tests/integration/test_login_flow.py b/tests/integration/test_login_flow.py index 7eb46ab39..a1df015bf 100644 --- a/tests/integration/test_login_flow.py +++ b/tests/integration/test_login_flow.py @@ -31,8 +31,8 @@ def LoginSample(): yield rx.redirect("/") def index(): - return rx.cond( - State.is_hydrated & State.auth_token, # type: ignore + return rx.cond( # pyright: ignore [reportCallIssue] + State.is_hydrated & State.auth_token, # pyright: ignore [reportOperatorIssue] rx.vstack( rx.heading(State.auth_token, id="auth-token"), rx.button("Logout", on_click=State.logout, id="logout"), diff --git a/tests/integration/test_media.py b/tests/integration/test_media.py index 649038a7e..f3ce65c87 100644 --- a/tests/integration/test_media.py +++ b/tests/integration/test_media.py @@ -19,7 +19,7 @@ def MediaApp(): def _blue(self, format=None) -> Image.Image: img = Image.new("RGB", (200, 200), "blue") if format is not None: - img.format = format # type: ignore + img.format = format return img @rx.var @@ -50,7 +50,7 @@ def MediaApp(): def img_from_url(self) -> Image.Image: img_url = "https://picsum.photos/id/1/200/300" img_resp = httpx.get(img_url, follow_redirects=True) - return Image.open(img_resp) # type: ignore + return Image.open(img_resp) # pyright: ignore [reportArgumentType] app = rx.App() diff --git a/tests/integration/test_state_inheritance.py b/tests/integration/test_state_inheritance.py index 6b93a2ed7..f544fcc92 100644 --- a/tests/integration/test_state_inheritance.py +++ b/tests/integration/test_state_inheritance.py @@ -131,7 +131,7 @@ def StateInheritance(): rx.heading(Base1.child_mixin, id="base1-child-mixin"), rx.button( "Base1.on_click_mixin", - on_click=Base1.on_click_mixin, # type: ignore + on_click=Base1.on_click_mixin, id="base1-mixin-btn", ), rx.heading( @@ -153,7 +153,7 @@ def StateInheritance(): 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 + on_click=Child1.on_click_other_mixin, id="child1-other-mixin-btn", ), # Child 2 (Mixin, ChildMixin, OtherMixin) @@ -166,12 +166,12 @@ def StateInheritance(): rx.heading(Child2.child_mixin, id="child2-child-mixin"), rx.button( "Child2.on_click_mixin", - on_click=Child2.on_click_mixin, # type: ignore + on_click=Child2.on_click_mixin, id="child2-mixin-btn", ), rx.button( "Child2.on_click_other_mixin", - on_click=Child2.on_click_other_mixin, # type: ignore + on_click=Child2.on_click_other_mixin, id="child2-other-mixin-btn", ), # Child 3 (Mixin, ChildMixin, OtherMixin) @@ -186,12 +186,12 @@ def StateInheritance(): rx.heading(Child3.child_mixin, id="child3-child-mixin"), rx.button( "Child3.on_click_mixin", - on_click=Child3.on_click_mixin, # type: ignore + on_click=Child3.on_click_mixin, id="child3-mixin-btn", ), rx.button( "Child3.on_click_other_mixin", - on_click=Child3.on_click_other_mixin, # type: ignore + on_click=Child3.on_click_other_mixin, id="child3-other-mixin-btn", ), rx.heading( diff --git a/tests/integration/test_upload.py b/tests/integration/test_upload.py index 6c7b4525e..deea5a892 100644 --- a/tests/integration/test_upload.py +++ b/tests/integration/test_upload.py @@ -80,7 +80,7 @@ def UploadFile(): ), rx.button( "Upload", - on_click=lambda: UploadState.handle_upload(rx.upload_files()), # type: ignore + on_click=lambda: UploadState.handle_upload(rx.upload_files()), # pyright: ignore [reportCallIssue] id="upload_button", ), rx.box( @@ -105,7 +105,7 @@ def UploadFile(): ), rx.button( "Upload", - on_click=UploadState.handle_upload_secondary( # type: ignore + on_click=UploadState.handle_upload_secondary( # pyright: ignore [reportCallIssue] rx.upload_files( upload_id="secondary", on_upload_progress=UploadState.upload_progress, @@ -127,7 +127,7 @@ def UploadFile(): ), rx.vstack( rx.foreach( - UploadState.progress_dicts, # type: ignore + UploadState.progress_dicts, lambda d: rx.text(d.to_string()), ) ), @@ -146,7 +146,7 @@ def UploadFile(): ), rx.button( "Upload", - on_click=UploadState.handle_upload_tertiary( # type: ignore + on_click=UploadState.handle_upload_tertiary( # pyright: ignore [reportCallIssue] rx.upload_files( upload_id="tertiary", ), diff --git a/tests/integration/test_var_operations.py b/tests/integration/test_var_operations.py index 10ccae0c1..9b952c575 100644 --- a/tests/integration/test_var_operations.py +++ b/tests/integration/test_var_operations.py @@ -7,13 +7,9 @@ from selenium.webdriver.common.by import By from reflex.testing import AppHarness -# pyright: reportOptionalMemberAccess=false, reportGeneralTypeIssues=false, reportUnknownMemberType=false - def VarOperations(): """App with var operations.""" - from typing import Dict, List - import reflex as rx from reflex.vars.base import LiteralVar from reflex.vars.sequence import ArrayVar @@ -27,24 +23,22 @@ def VarOperations(): int_var3: rx.Field[int] = rx.field(7) float_var1: rx.Field[float] = rx.field(10.5) float_var2: rx.Field[float] = rx.field(5.5) - list1: rx.Field[List[int]] = rx.field([1, 2]) - list2: rx.Field[List[int]] = rx.field([3, 4]) - list3: rx.Field[List[str]] = rx.field(["first", "second", "third"]) - list4: rx.Field[List[Object]] = rx.field( - [Object(name="obj_1"), Object(name="obj_2")] - ) + list1: rx.Field[list] = rx.field([1, 2]) + list2: rx.Field[list] = rx.field([3, 4]) + list3: rx.Field[list] = rx.field(["first", "second", "third"]) + list4: rx.Field[list] = rx.field([Object(name="obj_1"), Object(name="obj_2")]) str_var1: rx.Field[str] = rx.field("first") str_var2: rx.Field[str] = rx.field("second") str_var3: rx.Field[str] = rx.field("ThIrD") str_var4: rx.Field[str] = rx.field("a long string") - dict1: rx.Field[Dict[int, int]] = rx.field({1: 2}) - dict2: rx.Field[Dict[int, int]] = rx.field({3: 4}) + dict1: rx.Field[dict[int, int]] = rx.field({1: 2}) + dict2: rx.Field[dict[int, int]] = rx.field({3: 4}) html_str: rx.Field[str] = rx.field("
hello
") app = rx.App(_state=rx.State) @rx.memo - def memo_comp(list1: List[int], int_var1: int, id: str): + def memo_comp(list1: list[int], int_var1: int, id: str): return rx.text(list1, int_var1, id=id) @rx.memo @@ -380,7 +374,8 @@ def VarOperations(): id="str_contains", ), rx.text( - VarOperationState.str_var1 | VarOperationState.str_var1, id="str_or_str" + VarOperationState.str_var1 | VarOperationState.str_var1, + id="str_or_str", ), rx.text( VarOperationState.str_var1 & VarOperationState.str_var2, @@ -396,7 +391,8 @@ def VarOperations(): id="str_and_int", ), rx.text( - VarOperationState.str_var1 | VarOperationState.int_var2, id="str_or_int" + VarOperationState.str_var1 | VarOperationState.int_var2, + id="str_or_int", ), rx.text( (VarOperationState.str_var1 == VarOperationState.int_var1).to_string(), @@ -408,7 +404,8 @@ def VarOperations(): ), # STR, LIST rx.text( - VarOperationState.str_var1 | VarOperationState.list1, id="str_or_list" + VarOperationState.str_var1 | VarOperationState.list1, + id="str_or_list", ), rx.text( (VarOperationState.str_var1 & VarOperationState.list1).to_string(), @@ -424,7 +421,8 @@ def VarOperations(): ), # STR, DICT rx.text( - VarOperationState.str_var1 | VarOperationState.dict1, id="str_or_dict" + VarOperationState.str_var1 | VarOperationState.dict1, + id="str_or_dict", ), rx.text( (VarOperationState.str_var1 & VarOperationState.dict1).to_string(), @@ -476,7 +474,8 @@ def VarOperations(): id="list_neq_list", ), rx.text( - VarOperationState.list1.contains(1).to_string(), id="list_contains" + VarOperationState.list1.contains(1).to_string(), + id="list_contains", ), rx.text(VarOperationState.list4.pluck("name").to_string(), id="list_pluck"), rx.text(VarOperationState.list1.reverse().to_string(), id="list_reverse"), @@ -536,7 +535,8 @@ def VarOperations(): id="dict_neq_dict", ), rx.text( - VarOperationState.dict1.contains(1).to_string(), id="dict_contains" + VarOperationState.dict1.contains(1).to_string(), + id="dict_contains", ), rx.text(VarOperationState.str_var3.lower(), id="str_lower"), rx.text(VarOperationState.str_var3.upper(), id="str_upper"), @@ -573,7 +573,7 @@ def VarOperations(): ), rx.box( rx.foreach( - LiteralVar.create(list(range(0, 3))).to(ArrayVar, List[int]), + LiteralVar.create(list(range(0, 3))).to(ArrayVar, list[int]), lambda x: rx.foreach( ArrayVar.range(x), lambda y: rx.text(VarOperationState.list1[y], as_="p"), @@ -600,6 +600,11 @@ def VarOperations(): ), id="foreach_in_match", ), + # Literal range var in a foreach + rx.box(rx.foreach(range(42, 80, 27), rx.text.span), id="range_in_foreach1"), + rx.box(rx.foreach(range(42, 80, 3), rx.text.span), id="range_in_foreach2"), + rx.box(rx.foreach(range(42, 20, -6), rx.text.span), id="range_in_foreach3"), + rx.box(rx.foreach(range(42, 43, 5), rx.text.span), id="range_in_foreach4"), ) @@ -799,6 +804,11 @@ def test_var_operations(driver, var_operations: AppHarness): ("memo_comp_nested", "345"), # foreach in a match ("foreach_in_match", "first\nsecond\nthird"), + # literal range in a foreach + ("range_in_foreach1", "4269"), + ("range_in_foreach2", "42454851545760636669727578"), + ("range_in_foreach3", "42363024"), + ("range_in_foreach4", "42"), ] for tag, expected in tests: diff --git a/tests/integration/tests_playwright/test_appearance.py b/tests/integration/tests_playwright/test_appearance.py index 8e1c72fe3..0b1440ed1 100644 --- a/tests/integration/tests_playwright/test_appearance.py +++ b/tests/integration/tests_playwright/test_appearance.py @@ -85,7 +85,7 @@ def light_mode_app(tmp_path_factory) -> Generator[AppHarness, None, None]: """ with AppHarness.create( root=tmp_path_factory.mktemp("appearance_app"), - app_source=DefaultLightModeApp, # type: ignore + app_source=DefaultLightModeApp, ) as harness: assert harness.app_instance is not None, "app is not running" yield harness @@ -104,7 +104,7 @@ def dark_mode_app(tmp_path_factory) -> Generator[AppHarness, None, None]: """ with AppHarness.create( root=tmp_path_factory.mktemp("appearance_app"), - app_source=DefaultDarkModeApp, # type: ignore + app_source=DefaultDarkModeApp, ) as harness: assert harness.app_instance is not None, "app is not running" yield harness @@ -123,7 +123,7 @@ def system_mode_app(tmp_path_factory) -> Generator[AppHarness, None, None]: """ with AppHarness.create( root=tmp_path_factory.mktemp("appearance_app"), - app_source=DefaultSystemModeApp, # type: ignore + app_source=DefaultSystemModeApp, ) as harness: assert harness.app_instance is not None, "app is not running" yield harness @@ -142,7 +142,7 @@ def color_toggle_app(tmp_path_factory) -> Generator[AppHarness, None, None]: """ with AppHarness.create( root=tmp_path_factory.mktemp("appearance_app"), - app_source=ColorToggleApp, # type: ignore + app_source=ColorToggleApp, ) as harness: assert harness.app_instance is not None, "app is not running" yield harness diff --git a/tests/integration/tests_playwright/test_datetime_operations.py b/tests/integration/tests_playwright/test_datetime_operations.py index 5e4506776..2ac516d4a 100644 --- a/tests/integration/tests_playwright/test_datetime_operations.py +++ b/tests/integration/tests_playwright/test_datetime_operations.py @@ -54,7 +54,7 @@ def datetime_operations_app(tmp_path_factory) -> Generator[AppHarness, None, Non """ with AppHarness.create( root=tmp_path_factory.mktemp("datetime_operations_app"), - app_source=DatetimeOperationsApp, # type: ignore + app_source=DatetimeOperationsApp, ) as harness: assert harness.app_instance is not None, "app is not running" yield harness diff --git a/tests/integration/tests_playwright/test_link_hover.py b/tests/integration/tests_playwright/test_link_hover.py index 9510bd358..3c29d769a 100644 --- a/tests/integration/tests_playwright/test_link_hover.py +++ b/tests/integration/tests_playwright/test_link_hover.py @@ -29,7 +29,7 @@ def LinkApp(): def link_app(tmp_path_factory) -> Generator[AppHarness, None, None]: with AppHarness.create( root=tmp_path_factory.mktemp("link_app"), - app_source=LinkApp, # type: ignore + app_source=LinkApp, ) as harness: assert harness.app_instance is not None, "app is not running" yield harness diff --git a/tests/units/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py index 22f5c8483..50088e728 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -92,7 +92,7 @@ def test_compile_import_statement( ), ], ) -def test_compile_imports(import_dict: ParsedImportDict, test_dicts: List[dict]): +def test_compile_imports(import_dict: ParsedImportDict, test_dicts: list[dict]): """Test the compile_imports function. Args: @@ -100,10 +100,10 @@ def test_compile_imports(import_dict: ParsedImportDict, test_dicts: List[dict]): test_dicts: The expected output. """ imports = utils.compile_imports(import_dict) - for import_dict, test_dict in zip(imports, test_dicts): + for import_dict, test_dict in zip(imports, test_dicts, strict=True): assert import_dict["lib"] == test_dict["lib"] assert import_dict["default"] == test_dict["default"] - assert sorted(import_dict["rest"]) == test_dict["rest"] # type: ignore + assert sorted(import_dict["rest"]) == test_dict["rest"] # pyright: ignore [reportArgumentType] def test_compile_stylesheets(tmp_path, mocker): @@ -198,7 +198,7 @@ def test_create_document_root(): assert isinstance(root, utils.Html) assert isinstance(root.children[0], utils.DocumentHead) # Default language. - assert root.lang == "en" # type: ignore + assert root.lang == "en" # pyright: ignore [reportAttributeAccessIssue] # No children in head. assert len(root.children[0].children) == 0 @@ -208,13 +208,13 @@ def test_create_document_root(): utils.NextScript.create(src="bar.js"), ] root = utils.create_document_root( - head_components=comps, # type: ignore + head_components=comps, # pyright: ignore [reportArgumentType] html_lang="rx", html_custom_attrs={"project": "reflex"}, ) # Two children in head. assert isinstance(root, utils.Html) assert len(root.children[0].children) == 2 - assert root.lang == "rx" # type: ignore + assert root.lang == "rx" # pyright: ignore [reportAttributeAccessIssue] assert isinstance(root.custom_attrs, dict) assert root.custom_attrs == {"project": "reflex"} diff --git a/tests/units/components/core/test_colors.py b/tests/units/components/core/test_colors.py index c1295fb41..15490e576 100644 --- a/tests/units/components/core/test_colors.py +++ b/tests/units/components/core/test_colors.py @@ -31,37 +31,37 @@ def create_color_var(color): (create_color_var(rx.color("mint", 3)), '"var(--mint-3)"', Color), (create_color_var(rx.color("mint", 3, True)), '"var(--mint-a3)"', Color), ( - create_color_var(rx.color(ColorState.color, ColorState.shade)), # type: ignore + create_color_var(rx.color(ColorState.color, ColorState.shade)), # pyright: ignore [reportArgumentType] f'("var(--"+{color_state_name!s}.color+"-"+(((__to_string) => __to_string.toString())({color_state_name!s}.shade))+")")', Color, ), ( create_color_var( - rx.color(ColorState.color, ColorState.shade, ColorState.alpha) # type: ignore + rx.color(ColorState.color, ColorState.shade, ColorState.alpha) # pyright: ignore [reportArgumentType] ), f'("var(--"+{color_state_name!s}.color+"-"+({color_state_name!s}.alpha ? "a" : "")+(((__to_string) => __to_string.toString())({color_state_name!s}.shade))+")")', Color, ), ( - create_color_var(rx.color(f"{ColorState.color}", f"{ColorState.shade}")), # type: ignore + create_color_var(rx.color(f"{ColorState.color}", f"{ColorState.shade}")), # pyright: ignore [reportArgumentType] f'("var(--"+{color_state_name!s}.color+"-"+{color_state_name!s}.shade+")")', Color, ), ( create_color_var( - rx.color(f"{ColorState.color_part}ato", f"{ColorState.shade}") # type: ignore + rx.color(f"{ColorState.color_part}ato", f"{ColorState.shade}") # pyright: ignore [reportArgumentType] ), f'("var(--"+({color_state_name!s}.color_part+"ato")+"-"+{color_state_name!s}.shade+")")', Color, ), ( - create_color_var(f'{rx.color(ColorState.color, f"{ColorState.shade}")}'), # type: ignore + create_color_var(f'{rx.color(ColorState.color, f"{ColorState.shade}")}'), # pyright: ignore [reportArgumentType] f'("var(--"+{color_state_name!s}.color+"-"+{color_state_name!s}.shade+")")', str, ), ( create_color_var( - f'{rx.color(f"{ColorState.color}", f"{ColorState.shade}")}' # type: ignore + f'{rx.color(f"{ColorState.color}", f"{ColorState.shade}")}' # pyright: ignore [reportArgumentType] ), f'("var(--"+{color_state_name!s}.color+"-"+{color_state_name!s}.shade+")")', str, @@ -81,7 +81,7 @@ def test_color(color, expected, expected_type: Union[Type[str], Type[Color]]): '(true ? "var(--mint-7)" : "var(--tomato-5)")', ), ( - rx.cond(True, rx.color(ColorState.color), rx.color(ColorState.color, 5)), # type: ignore + rx.cond(True, rx.color(ColorState.color), rx.color(ColorState.color, 5)), # pyright: ignore [reportArgumentType, reportCallIssue] f'(true ? ("var(--"+{color_state_name!s}.color+"-7)") : ("var(--"+{color_state_name!s}.color+"-5)"))', ), ( @@ -89,7 +89,7 @@ def test_color(color, expected, expected_type: Union[Type[str], Type[Color]]): "condition", ("first", rx.color("mint")), ("second", rx.color("tomato", 5)), - rx.color(ColorState.color, 2), # type: ignore + rx.color(ColorState.color, 2), # pyright: ignore [reportArgumentType] ), '(() => { switch (JSON.stringify("condition")) {case JSON.stringify("first"): return ("var(--mint-7)");' ' break;case JSON.stringify("second"): return ("var(--tomato-5)"); break;default: ' @@ -98,9 +98,9 @@ def test_color(color, expected, expected_type: Union[Type[str], Type[Color]]): ( rx.match( "condition", - ("first", rx.color(ColorState.color)), # type: ignore - ("second", rx.color(ColorState.color, 5)), # type: ignore - rx.color(ColorState.color, 2), # type: ignore + ("first", rx.color(ColorState.color)), # pyright: ignore [reportArgumentType] + ("second", rx.color(ColorState.color, 5)), # pyright: ignore [reportArgumentType] + rx.color(ColorState.color, 2), # pyright: ignore [reportArgumentType] ), '(() => { switch (JSON.stringify("condition")) {case JSON.stringify("first"): ' f'return (("var(--"+{color_state_name!s}.color+"-7)")); break;case JSON.stringify("second"): ' @@ -133,4 +133,4 @@ def test_radix_color(color, expected): expected (str): The expected custom_style string, radix or literal """ code_block = CodeBlock.create("Hello World", background_color=color) - assert str(code_block.custom_style["backgroundColor"]) == expected # type: ignore + assert str(code_block.custom_style["backgroundColor"]) == expected # pyright: ignore [reportAttributeAccessIssue] diff --git a/tests/units/components/core/test_cond.py b/tests/units/components/core/test_cond.py index 64ef88833..1fe1061f5 100644 --- a/tests/units/components/core/test_cond.py +++ b/tests/units/components/core/test_cond.py @@ -13,7 +13,7 @@ from reflex.vars.base import LiteralVar, Var, computed_var @pytest.fixture def cond_state(request): class CondState(BaseState): - value: request.param["value_type"] = request.param["value"] # pyright: ignore[reportInvalidTypeForm, reportUndefinedVariable] # noqa: F821 + value: request.param["value_type"] = request.param["value"] # pyright: ignore [reportInvalidTypeForm, reportUndefinedVariable] # noqa: F821 return CondState @@ -111,7 +111,7 @@ def test_cond_no_else(): # Props do not support the use of cond without else with pytest.raises(ValueError): - cond(True, "hello") # type: ignore + cond(True, "hello") # pyright: ignore [reportArgumentType] def test_cond_computed_var(): diff --git a/tests/units/components/core/test_html.py b/tests/units/components/core/test_html.py index 79c258dfb..bebb2587d 100644 --- a/tests/units/components/core/test_html.py +++ b/tests/units/components/core/test_html.py @@ -16,7 +16,7 @@ def test_html_many_children(): def test_html_create(): html = Html.create("

Hello !

") - assert str(html.dangerouslySetInnerHTML) == '({ ["__html"] : "

Hello !

" })' # type: ignore + assert str(html.dangerouslySetInnerHTML) == '({ ["__html"] : "

Hello !

" })' # pyright: ignore [reportAttributeAccessIssue] assert ( str(html) == '
Hello !

" })}/>' @@ -32,10 +32,10 @@ def test_html_fstring_create(): html = Html.create(f"

Hello {TestState.myvar}!

") assert ( - str(html.dangerouslySetInnerHTML) # type: ignore + str(html.dangerouslySetInnerHTML) # pyright: ignore [reportAttributeAccessIssue] == f'({{ ["__html"] : ("

Hello "+{TestState.myvar!s}+"!

") }})' ) assert ( str(html) - == f'
' # type: ignore + == f'
' # pyright: ignore [reportAttributeAccessIssue] ) diff --git a/tests/units/components/core/test_upload.py b/tests/units/components/core/test_upload.py index 710baa161..efade7b63 100644 --- a/tests/units/components/core/test_upload.py +++ b/tests/units/components/core/test_upload.py @@ -5,7 +5,7 @@ from reflex.components.core.upload import ( StyledUpload, Upload, UploadNamespace, - _on_drop_spec, # type: ignore + _on_drop_spec, # pyright: ignore [reportAttributeAccessIssue] cancel_upload, get_upload_url, ) @@ -60,7 +60,7 @@ def test_upload_create(): up_comp_2 = Upload.create( id="foo_id", - on_drop=UploadStateTest.drop_handler([]), # type: ignore + on_drop=UploadStateTest.drop_handler([]), ) assert isinstance(up_comp_2, Upload) assert up_comp_2.is_used @@ -80,7 +80,7 @@ def test_upload_create(): up_comp_4 = Upload.create( id="foo_id", - on_drop=UploadStateTest.not_drop_handler([]), # type: ignore + on_drop=UploadStateTest.not_drop_handler([]), ) assert isinstance(up_comp_4, Upload) assert up_comp_4.is_used @@ -96,7 +96,7 @@ def test_styled_upload_create(): styled_up_comp_2 = StyledUpload.create( id="foo_id", - on_drop=UploadStateTest.drop_handler([]), # type: ignore + on_drop=UploadStateTest.drop_handler([]), ) assert isinstance(styled_up_comp_2, StyledUpload) assert styled_up_comp_2.is_used @@ -116,7 +116,7 @@ def test_styled_upload_create(): styled_up_comp_4 = StyledUpload.create( id="foo_id", - on_drop=UploadStateTest.not_drop_handler([]), # type: ignore + on_drop=UploadStateTest.not_drop_handler([]), ) assert isinstance(styled_up_comp_4, StyledUpload) assert styled_up_comp_4.is_used diff --git a/tests/units/components/datadisplay/test_code.py b/tests/units/components/datadisplay/test_code.py index 6b7168756..db0120fe1 100644 --- a/tests/units/components/datadisplay/test_code.py +++ b/tests/units/components/datadisplay/test_code.py @@ -10,4 +10,4 @@ from reflex.components.datadisplay.code import CodeBlock, Theme def test_code_light_dark_theme(theme, expected): code_block = CodeBlock.create(theme=theme) - assert code_block.theme._js_expr == expected # type: ignore + assert code_block.theme._js_expr == expected # pyright: ignore [reportAttributeAccessIssue] diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py index eb473ba06..cc05c35b0 100644 --- a/tests/units/components/datadisplay/test_shiki_code.py +++ b/tests/units/components/datadisplay/test_shiki_code.py @@ -95,7 +95,7 @@ def test_create_shiki_code_block( # Test that the first child is the code code_block_component = component.children[0] - assert code_block_component.code._var_value == expected_first_child # type: ignore + assert code_block_component.code._var_value == expected_first_child # pyright: ignore [reportAttributeAccessIssue] applied_styles = component.style for key, value in expected_styles.items(): @@ -128,12 +128,12 @@ def test_create_shiki_high_level_code_block( # Test that the first child is the code block component code_block_component = component.children[0] - assert code_block_component.code._var_value == children[0] # type: ignore + assert code_block_component.code._var_value == children[0] # pyright: ignore [reportAttributeAccessIssue] # Check if the transformer is set correctly if expected if expected_transformers: exp_trans_names = [t.__name__ for t in expected_transformers] - for transformer in code_block_component.transformers._var_value: # type: ignore + for transformer in code_block_component.transformers._var_value: # pyright: ignore [reportAttributeAccessIssue] assert type(transformer).__name__ in exp_trans_names # Check if the second child is the copy button if can_copy is True @@ -161,12 +161,12 @@ def test_shiki_high_level_code_block_theme_language_mapping(children, props): if "theme" in props: assert component.children[ 0 - ].theme._var_value == ShikiHighLevelCodeBlock._map_themes(props["theme"]) # type: ignore + ].theme._var_value == ShikiHighLevelCodeBlock._map_themes(props["theme"]) # pyright: ignore [reportAttributeAccessIssue] # Test that the language is mapped correctly if "language" in props: assert component.children[ 0 - ].language._var_value == ShikiHighLevelCodeBlock._map_languages( # type: ignore + ].language._var_value == ShikiHighLevelCodeBlock._map_languages( # pyright: ignore [reportAttributeAccessIssue] props["language"] ) diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index 5f3ba2d37..69b5e7b63 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -10,7 +10,7 @@ def test_render_on_submit(): _var_type=EventChain, ) f = Form.create(on_submit=submit_it) - exp_submit_name = f"handleSubmit_{f.handle_submit_unique_name}" # type: ignore + exp_submit_name = f"handleSubmit_{f.handle_submit_unique_name}" # pyright: ignore [reportAttributeAccessIssue] assert f"onSubmit={{{exp_submit_name}}}" in f.render()["props"] diff --git a/tests/units/components/media/test_image.py b/tests/units/components/media/test_image.py index 742bd8c38..519ca735e 100644 --- a/tests/units/components/media/test_image.py +++ b/tests/units/components/media/test_image.py @@ -4,7 +4,7 @@ import pytest from PIL.Image import Image as Img import reflex as rx -from reflex.components.next.image import Image # type: ignore +from reflex.components.next.image import Image from reflex.utils.serializers import serialize, serialize_image from reflex.vars.sequence import StringVar @@ -17,7 +17,7 @@ def pil_image() -> Img: A random PIL image. """ imarray = np.random.rand(100, 100, 3) * 255 - return PIL.Image.fromarray(imarray.astype("uint8")).convert("RGBA") # type: ignore + return PIL.Image.fromarray(imarray.astype("uint8")).convert("RGBA") # pyright: ignore [reportAttributeAccessIssue] def test_serialize_image(pil_image: Img): @@ -36,13 +36,13 @@ def test_set_src_str(): """Test that setting the src works.""" image = rx.image(src="pic2.jpeg") # when using next/image, we explicitly create a _var_is_str Var - assert str(image.src) in ( # type: ignore + assert str(image.src) in ( # pyright: ignore [reportAttributeAccessIssue] '"pic2.jpeg"', "'pic2.jpeg'", "`pic2.jpeg`", ) # For plain rx.el.img, an explicit var is not created, so the quoting happens later - # assert str(image.src) == "pic2.jpeg" # type: ignore #noqa: ERA001 + # assert str(image.src) == "pic2.jpeg" #noqa: ERA001 def test_set_src_img(pil_image: Img): @@ -52,7 +52,7 @@ def test_set_src_img(pil_image: Img): pil_image: The image to serialize. """ image = Image.create(src=pil_image) - assert str(image.src._js_expr) == '"' + serialize_image(pil_image) + '"' # type: ignore + assert str(image.src._js_expr) == '"' + serialize_image(pil_image) + '"' # pyright: ignore [reportAttributeAccessIssue] def test_render(pil_image: Img): @@ -62,4 +62,4 @@ def test_render(pil_image: Img): pil_image: The image to serialize. """ image = Image.create(src=pil_image) - assert isinstance(image.src, StringVar) # type: ignore + assert isinstance(image.src, StringVar) # pyright: ignore [reportAttributeAccessIssue] diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 2ea85b235..ca5a904ba 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -36,6 +36,7 @@ from reflex.utils.exceptions import ( from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var +from reflex.vars.object import ObjectVar @pytest.fixture @@ -452,8 +453,8 @@ def test_add_style(component1, component2): component1: Style({"color": "white"}), component2: Style({"color": "black"}), } - c1 = component1()._add_style_recursive(style) # type: ignore - c2 = component2()._add_style_recursive(style) # type: ignore + c1 = component1()._add_style_recursive(style) + c2 = component2()._add_style_recursive(style) assert str(c1.style["color"]) == '"white"' assert str(c2.style["color"]) == '"black"' @@ -469,8 +470,8 @@ def test_add_style_create(component1, component2): component1.create: Style({"color": "white"}), component2.create: Style({"color": "black"}), } - c1 = component1()._add_style_recursive(style) # type: ignore - c2 = component2()._add_style_recursive(style) # type: ignore + c1 = component1()._add_style_recursive(style) + c2 = component2()._add_style_recursive(style) assert str(c1.style["color"]) == '"white"' assert str(c2.style["color"]) == '"black"' @@ -842,12 +843,12 @@ def test_component_event_trigger_arbitrary_args(): """Test that we can define arbitrary types for the args of an event trigger.""" def on_foo_spec( - _e: Var[JavascriptInputEvent], + _e: ObjectVar[JavascriptInputEvent], alpha: Var[str], bravo: dict[str, Any], - charlie: Var[_Obj], + charlie: ObjectVar[_Obj], ): - return [_e.target.value, bravo["nested"], charlie.custom + 42] + return [_e.target.value, bravo["nested"], charlie.custom.to(int) + 42] class C1(Component): library = "/local" @@ -1328,7 +1329,7 @@ class EventState(rx.State): ), pytest.param( rx.fragment(class_name=[TEST_VAR, "other-class"]), - [LiteralVar.create([TEST_VAR, "other-class"]).join(" ")], + [Var.create([TEST_VAR, "other-class"]).join(" ")], id="fstring-dual-class_name", ), pytest.param( @@ -1363,17 +1364,17 @@ class EventState(rx.State): id="fstring-background_color", ), pytest.param( - rx.fragment(style={"background_color": TEST_VAR}), # type: ignore + rx.fragment(style={"background_color": TEST_VAR}), # pyright: ignore [reportArgumentType] [STYLE_VAR], id="direct-style-background_color", ), pytest.param( - rx.fragment(style={"background_color": f"foo{TEST_VAR}bar"}), # type: ignore + rx.fragment(style={"background_color": f"foo{TEST_VAR}bar"}), # pyright: ignore [reportArgumentType] [STYLE_VAR], id="fstring-style-background_color", ), pytest.param( - rx.fragment(on_click=EVENT_CHAIN_VAR), # type: ignore + rx.fragment(on_click=EVENT_CHAIN_VAR), [EVENT_CHAIN_VAR], id="direct-event-chain", ), @@ -1383,17 +1384,17 @@ class EventState(rx.State): id="direct-event-handler", ), pytest.param( - rx.fragment(on_click=EventState.handler2(TEST_VAR)), # type: ignore + rx.fragment(on_click=EventState.handler2(TEST_VAR)), # pyright: ignore [reportCallIssue] [ARG_VAR, TEST_VAR], id="direct-event-handler-arg", ), pytest.param( - rx.fragment(on_click=EventState.handler2(EventState.v)), # type: ignore + rx.fragment(on_click=EventState.handler2(EventState.v)), # pyright: ignore [reportCallIssue] [ARG_VAR, EventState.v], id="direct-event-handler-arg2", ), pytest.param( - rx.fragment(on_click=lambda: EventState.handler2(TEST_VAR)), # type: ignore + rx.fragment(on_click=lambda: EventState.handler2(TEST_VAR)), # pyright: ignore [reportCallIssue] [ARG_VAR, TEST_VAR], id="direct-event-handler-lambda", ), @@ -1446,6 +1447,7 @@ def test_get_vars(component, exp_vars): for comp_var, exp_var in zip( comp_vars, sorted(exp_vars, key=lambda v: v._js_expr), + strict=True, ): assert comp_var.equals(exp_var) @@ -1478,7 +1480,7 @@ def test_instantiate_all_components(): comp_name for submodule_list in component_nested_list for comp_name in submodule_list - ]: # type: ignore + ]: if component_name in untested_components: continue component = getattr( @@ -1551,11 +1553,11 @@ def test_validate_valid_children(): ) valid_component1( - rx.cond( # type: ignore + rx.cond( True, rx.fragment(valid_component2()), rx.fragment( - rx.foreach(LiteralVar.create([1, 2, 3]), lambda x: valid_component2(x)) # type: ignore + rx.foreach(LiteralVar.create([1, 2, 3]), lambda x: valid_component2(x)) ), ) ) @@ -1610,12 +1612,12 @@ def test_validate_valid_parents(): ) valid_component2( - rx.cond( # type: ignore + rx.cond( True, rx.fragment(valid_component3()), rx.fragment( rx.foreach( - LiteralVar.create([1, 2, 3]), # type: ignore + LiteralVar.create([1, 2, 3]), lambda x: valid_component2(valid_component3(x)), ) ), @@ -1678,13 +1680,13 @@ def test_validate_invalid_children(): with pytest.raises(ValueError): valid_component4( - rx.cond( # type: ignore + rx.cond( True, rx.fragment(invalid_component()), rx.fragment( rx.foreach( LiteralVar.create([1, 2, 3]), lambda x: invalid_component(x) - ) # type: ignore + ) ), ) ) @@ -1824,6 +1826,7 @@ def test_custom_component_declare_event_handlers_in_fields(): for v1, v2 in zip( parse_args_spec(test_triggers[trigger_name]), parse_args_spec(custom_triggers[trigger_name]), + strict=True, ): assert v1.equals(v2) @@ -1865,7 +1868,7 @@ def test_invalid_event_trigger(): ) def test_component_add_imports(tags): class BaseComponent(Component): - def _get_imports(self) -> ImportDict: + def _get_imports(self) -> ImportDict: # pyright: ignore [reportIncompatibleMethodOverride] return {} class Reference(Component): @@ -1877,7 +1880,7 @@ def test_component_add_imports(tags): ) class TestBase(Component): - def add_imports( + def add_imports( # pyright: ignore [reportIncompatibleMethodOverride] self, ) -> Dict[str, Union[str, ImportVar, List[str], List[ImportVar]]]: return {"foo": "bar"} @@ -1909,7 +1912,7 @@ def test_component_add_hooks(): pass class GrandchildComponent1(ChildComponent1): - def add_hooks(self): + def add_hooks(self): # pyright: ignore [reportIncompatibleMethodOverride] return [ "const hook2 = 43", "const hook3 = 44", @@ -1922,11 +1925,11 @@ def test_component_add_hooks(): ] class GrandchildComponent2(ChildComponent1): - def _get_hooks(self): + def _get_hooks(self): # pyright: ignore [reportIncompatibleMethodOverride] return "const hook5 = 46" class GreatGrandchildComponent2(GrandchildComponent2): - def add_hooks(self): + def add_hooks(self): # pyright: ignore [reportIncompatibleMethodOverride] return [ "const hook2 = 43", "const hook6 = 47", @@ -2001,7 +2004,7 @@ def test_component_add_custom_code(): ] class GrandchildComponent2(ChildComponent1): - def _get_custom_code(self): + def _get_custom_code(self): # pyright: ignore [reportIncompatibleMethodOverride] return "const custom_code5 = 46" class GreatGrandchildComponent2(GrandchildComponent2): @@ -2097,11 +2100,11 @@ def test_add_style_embedded_vars(test_state: BaseState): test_state: A test state. """ v0 = LiteralVar.create("parent")._replace( - merge_var_data=VarData(hooks={"useParent": None}), # type: ignore + merge_var_data=VarData(hooks={"useParent": None}), ) v1 = rx.color("plum", 10) v2 = LiteralVar.create("text")._replace( - merge_var_data=VarData(hooks={"useText": None}), # type: ignore + merge_var_data=VarData(hooks={"useText": None}), ) class ParentComponent(Component): @@ -2115,7 +2118,7 @@ def test_add_style_embedded_vars(test_state: BaseState): class StyledComponent(ParentComponent): tag = "StyledComponent" - def add_style(self): + def add_style(self): # pyright: ignore [reportIncompatibleMethodOverride] return { "color": v1, "fake": v2, diff --git a/tests/units/components/typography/test_markdown.py b/tests/units/components/typography/test_markdown.py index 5e9abbb1f..12f3b0dbe 100644 --- a/tests/units/components/typography/test_markdown.py +++ b/tests/units/components/typography/test_markdown.py @@ -29,8 +29,8 @@ def test_get_component(tag, expected): expected: The expected component. """ md = Markdown.create("# Hello") - assert tag in md.component_map # type: ignore - assert md.get_component(tag).tag == expected # type: ignore + assert tag in md.component_map # pyright: ignore [reportAttributeAccessIssue] + assert md.get_component(tag).tag == expected def test_set_component_map(): @@ -42,8 +42,8 @@ def test_set_component_map(): md = Markdown.create("# Hello", component_map=component_map) # Check that the new tags have been added. - assert md.get_component("h1").tag == "Box" # type: ignore - assert md.get_component("p").tag == "Box" # type: ignore + assert md.get_component("h1").tag == "Box" + assert md.get_component("p").tag == "Box" # Make sure the old tags are still there. - assert md.get_component("h2").tag == "Heading" # type: ignore + assert md.get_component("h2").tag == "Heading" diff --git a/tests/units/conftest.py b/tests/units/conftest.py index 8c1ffe532..2ee290ea3 100644 --- a/tests/units/conftest.py +++ b/tests/units/conftest.py @@ -95,7 +95,7 @@ def upload_sub_state_event_spec(): Returns: Event Spec. """ - return EventSpec(handler=SubUploadState.handle_upload, upload=True) # type: ignore + return EventSpec(handler=SubUploadState.handle_upload, upload=True) # pyright: ignore [reportCallIssue] @pytest.fixture @@ -105,7 +105,7 @@ def upload_event_spec(): Returns: Event Spec. """ - return EventSpec(handler=UploadState.handle_upload1, upload=True) # type: ignore + return EventSpec(handler=UploadState.handle_upload1, upload=True) # pyright: ignore [reportCallIssue] @pytest.fixture @@ -143,7 +143,7 @@ def sqlite_db_config_values(base_db_config_values) -> Dict: @pytest.fixture -def router_data_headers() -> Dict[str, str]: +def router_data_headers() -> dict[str, str]: """Router data headers. Returns: @@ -170,7 +170,7 @@ def router_data_headers() -> Dict[str, str]: @pytest.fixture -def router_data(router_data_headers) -> Dict[str, str]: +def router_data(router_data_headers: dict[str, str]) -> dict[str, str | dict]: """Router data. Args: @@ -179,7 +179,7 @@ def router_data(router_data_headers) -> Dict[str, str]: Returns: Dict of router data. """ - return { # type: ignore + return { "pathname": "/", "query": {}, "token": "b181904c-3953-4a79-dc18-ae9518c22f05", diff --git a/tests/units/states/mutation.py b/tests/units/states/mutation.py index b05f558a1..ad658bbd0 100644 --- a/tests/units/states/mutation.py +++ b/tests/units/states/mutation.py @@ -18,7 +18,7 @@ class DictMutationTestState(BaseState): def add_age(self): """Add an age to the dict.""" - self.details.update({"age": 20}) # type: ignore + self.details.update({"age": 20}) # pyright: ignore [reportCallIssue, reportArgumentType] def change_name(self): """Change the name in the dict.""" diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 92228439d..08726cefa 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -132,7 +132,7 @@ def test_model() -> Type[Model]: A default model. """ - class TestModel(Model, table=True): # type: ignore + class TestModel(Model, table=True): pass return TestModel @@ -146,7 +146,7 @@ def test_model_auth() -> Type[Model]: A default model. """ - class TestModelAuth(Model, table=True): # type: ignore + class TestModelAuth(Model, table=True): """A test model with auth.""" pass @@ -184,19 +184,19 @@ def test_custom_auth_admin() -> Type[AuthProvider]: login_path: str = "/login" logout_path: str = "/logout" - def login(self): + def login(self): # pyright: ignore [reportIncompatibleMethodOverride] """Login.""" pass - def is_authenticated(self): + def is_authenticated(self): # pyright: ignore [reportIncompatibleMethodOverride] """Is authenticated.""" pass - def get_admin_user(self): + def get_admin_user(self): # pyright: ignore [reportIncompatibleMethodOverride] """Get admin user.""" pass - def logout(self): + def logout(self): # pyright: ignore [reportIncompatibleMethodOverride] """Logout.""" pass @@ -418,7 +418,7 @@ async def test_initialize_with_state(test_state: Type[ATestState], token: str): # Get a state for a given token. state = await app.state_manager.get_state(_substate_key(token, test_state)) assert isinstance(state, test_state) - assert state.var == 0 # type: ignore + assert state.var == 0 if isinstance(app.state_manager, StateManagerRedis): await app.state_manager.close() @@ -440,8 +440,8 @@ async def test_set_and_get_state(test_state): # Get the default state for each token. state1 = await app.state_manager.get_state(token1) state2 = await app.state_manager.get_state(token2) - assert state1.var == 0 # type: ignore - assert state2.var == 0 # type: ignore + assert state1.var == 0 + assert state2.var == 0 # Set the vars to different values. state1.var = 1 @@ -452,8 +452,8 @@ async def test_set_and_get_state(test_state): # Get the states again and check the values. state1 = await app.state_manager.get_state(token1) state2 = await app.state_manager.get_state(token2) - assert state1.var == 1 # type: ignore - assert state2.var == 2 # type: ignore + assert state1.var == 1 + assert state2.var == 2 if isinstance(app.state_manager, StateManagerRedis): await app.state_manager.close() @@ -468,17 +468,17 @@ async def test_dynamic_var_event(test_state: Type[ATestState], token: str): test_state: State Fixture. token: a Token. """ - state = test_state() # type: ignore + state = test_state() # pyright: ignore [reportCallIssue] state.add_var("int_val", int, 0) - result = await state._process( + async for result in state._process( Event( token=token, name=f"{test_state.get_name()}.set_int_val", router_data={"pathname": "/", "query": {}}, payload={"value": 50}, ) - ).__anext__() - assert result.delta == {test_state.get_name(): {"int_val": 50}} + ): + assert result.delta == {test_state.get_name(): {"int_val": 50}} @pytest.mark.asyncio @@ -582,18 +582,17 @@ async def test_list_mutation_detection__plain_list( token: a Token. """ for event_name, expected_delta in event_tuples: - result = await list_mutation_state._process( + async for result in list_mutation_state._process( Event( token=token, name=f"{list_mutation_state.get_name()}.{event_name}", router_data={"pathname": "/", "query": {}}, payload={}, ) - ).__anext__() - - # prefix keys in expected_delta with the state name - expected_delta = {list_mutation_state.get_name(): expected_delta} - assert result.delta == expected_delta + ): + # prefix keys in expected_delta with the state name + expected_delta = {list_mutation_state.get_name(): expected_delta} + assert result.delta == expected_delta @pytest.mark.asyncio @@ -708,19 +707,18 @@ async def test_dict_mutation_detection__plain_list( token: a Token. """ for event_name, expected_delta in event_tuples: - result = await dict_mutation_state._process( + async for result in dict_mutation_state._process( Event( token=token, name=f"{dict_mutation_state.get_name()}.{event_name}", router_data={"pathname": "/", "query": {}}, payload={}, ) - ).__anext__() + ): + # prefix keys in expected_delta with the state name + expected_delta = {dict_mutation_state.get_name(): expected_delta} - # prefix keys in expected_delta with the state name - expected_delta = {dict_mutation_state.get_name(): expected_delta} - - assert result.delta == expected_delta + assert result.delta == expected_delta @pytest.mark.asyncio @@ -771,7 +769,7 @@ async def test_upload_file(tmp_path, state, delta, token: str, mocker): # The App state must be the "root" of the state tree app = App() app._enable_state() - app.event_namespace.emit = AsyncMock() # type: ignore + app.event_namespace.emit = AsyncMock() # pyright: ignore [reportOptionalMemberAccess] current_state = await app.state_manager.get_state(_substate_key(token, state)) data = b"This is binary data" @@ -794,7 +792,7 @@ async def test_upload_file(tmp_path, state, delta, token: str, mocker): file=bio, ) upload_fn = upload(app) - streaming_response = await upload_fn(request_mock, [file1, file2]) + streaming_response = await upload_fn(request_mock, [file1, file2]) # pyright: ignore [reportFunctionMemberAccess] async for state_update in streaming_response.body_iterator: assert ( state_update @@ -940,7 +938,7 @@ def test_dynamic_arg_shadow( app = app_module_mock.app = App(_state=DynamicState) assert app._state is not None with pytest.raises(NameError): - app.add_page(index_page, route=route, on_load=DynamicState.on_load) # type: ignore + app.add_page(index_page, route=route, on_load=DynamicState.on_load) def test_multiple_dynamic_args( @@ -992,7 +990,7 @@ async def test_dynamic_route_var_route_change_completed_on_load( app = app_module_mock.app = App(_state=DynamicState) assert app._state is not None assert arg_name not in app._state.vars - app.add_page(index_page, route=route, on_load=DynamicState.on_load) # type: ignore + app.add_page(index_page, route=route, on_load=DynamicState.on_load) assert arg_name in app._state.vars assert arg_name in app._state.computed_vars assert app._state.computed_vars[arg_name]._deps(objclass=DynamicState) == { @@ -1021,7 +1019,7 @@ async def test_dynamic_route_var_route_change_completed_on_load( def _dynamic_state_event(name, val, **kwargs): return _event( - name=format.format_event_handler(getattr(DynamicState, name)), # type: ignore + name=format.format_event_handler(getattr(DynamicState, name)), val=val, **kwargs, ) @@ -1225,12 +1223,12 @@ def test_overlay_component( assert app.overlay_component is None elif isinstance(exp_page_child, OverlayFragment): assert app.overlay_component is not None - generated_component = app._generate_component(app.overlay_component) # type: ignore + generated_component = app._generate_component(app.overlay_component) assert isinstance(generated_component, OverlayFragment) else: assert app.overlay_component is not None assert isinstance( - app._generate_component(app.overlay_component), # type: ignore + app._generate_component(app.overlay_component), exp_page_child, ) @@ -1310,19 +1308,19 @@ def test_app_wrap_priority(compilable_app: tuple[App, Path]): class Fragment1(Component): tag = "Fragment1" - def _get_app_wrap_components(self) -> dict[tuple[int, str], Component]: + def _get_app_wrap_components(self) -> dict[tuple[int, str], Component]: # pyright: ignore [reportIncompatibleMethodOverride] return {(99, "Box"): rx.box()} class Fragment2(Component): tag = "Fragment2" - def _get_app_wrap_components(self) -> dict[tuple[int, str], Component]: + def _get_app_wrap_components(self) -> dict[tuple[int, str], Component]: # pyright: ignore [reportIncompatibleMethodOverride] return {(50, "Text"): rx.text()} class Fragment3(Component): tag = "Fragment3" - def _get_app_wrap_components(self) -> dict[tuple[int, str], Component]: + def _get_app_wrap_components(self) -> dict[tuple[int, str], Component]: # pyright: ignore [reportIncompatibleMethodOverride] return {(10, "Fragment2"): Fragment2.create()} def page(): @@ -1443,11 +1441,11 @@ def test_generate_component(): "Bar", ) - comp = App._generate_component(index) # type: ignore + comp = App._generate_component(index) assert isinstance(comp, Component) with pytest.raises(exceptions.MatchTypeError): - App._generate_component(index_mismatch) # type: ignore + App._generate_component(index_mismatch) # pyright: ignore [reportArgumentType] def test_add_page_component_returning_tuple(): @@ -1462,8 +1460,8 @@ def test_add_page_component_returning_tuple(): def page2(): return (rx.text("third"),) - app.add_page(index) # type: ignore - app.add_page(page2) # type: ignore + app.add_page(index) # pyright: ignore [reportArgumentType] + app.add_page(page2) # pyright: ignore [reportArgumentType] app._compile_page("index") app._compile_page("page2") @@ -1472,17 +1470,17 @@ def test_add_page_component_returning_tuple(): assert isinstance(fragment_wrapper, Fragment) first_text = fragment_wrapper.children[0] assert isinstance(first_text, Text) - assert str(first_text.children[0].contents) == '"first"' # type: ignore + assert str(first_text.children[0].contents) == '"first"' # pyright: ignore [reportAttributeAccessIssue] second_text = fragment_wrapper.children[1] assert isinstance(second_text, Text) - assert str(second_text.children[0].contents) == '"second"' # type: ignore + assert str(second_text.children[0].contents) == '"second"' # pyright: ignore [reportAttributeAccessIssue] # Test page with trailing comma. page2_fragment_wrapper = app._pages["page2"].children[0] assert isinstance(page2_fragment_wrapper, Fragment) third_text = page2_fragment_wrapper.children[0] assert isinstance(third_text, Text) - assert str(third_text.children[0].contents) == '"third"' # type: ignore + assert str(third_text.children[0].contents) == '"third"' # pyright: ignore [reportAttributeAccessIssue] @pytest.mark.parametrize("export", (True, False)) @@ -1520,7 +1518,7 @@ def test_app_with_transpile_packages(compilable_app: tuple[App, Path], export: b next_config = (web_dir / "next.config.js").read_text() transpile_packages_match = re.search(r"transpilePackages: (\[.*?\])", next_config) - transpile_packages_json = transpile_packages_match.group(1) # type: ignore + transpile_packages_json = transpile_packages_match.group(1) # pyright: ignore [reportOptionalMemberAccess] transpile_packages = sorted(json.loads(transpile_packages_json)) assert transpile_packages == [ diff --git a/tests/units/test_config.py b/tests/units/test_config.py index e5d4622bd..88d8b5f2f 100644 --- a/tests/units/test_config.py +++ b/tests/units/test_config.py @@ -21,7 +21,7 @@ from reflex.constants import Endpoint, Env def test_requires_app_name(): """Test that a config requires an app_name.""" with pytest.raises(ValueError): - rx.Config() # type: ignore + rx.Config() def test_set_app_name(base_config_values): @@ -207,7 +207,7 @@ def test_replace_defaults( exp_config_values: The expected config values. """ mock_os_env = os.environ.copy() - monkeypatch.setattr(reflex.config.os, "environ", mock_os_env) # type: ignore + monkeypatch.setattr(reflex.config.os, "environ", mock_os_env) mock_os_env.update({k: str(v) for k, v in env_vars.items()}) c = rx.Config(app_name="a", **config_kwargs) c._set_persistent(**set_persistent_vars) diff --git a/tests/units/test_event.py b/tests/units/test_event.py index f932010b8..e127ac853 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -3,6 +3,7 @@ from typing import Callable, List import pytest import reflex as rx +from reflex.constants.compiler import Hooks, Imports from reflex.event import ( Event, EventActionsMixin, @@ -15,7 +16,7 @@ from reflex.event import ( ) from reflex.state import BaseState from reflex.utils import format -from reflex.vars.base import Field, LiteralVar, Var, field +from reflex.vars.base import Field, LiteralVar, Var, VarData, field def make_var(value) -> Var: @@ -73,7 +74,7 @@ def test_call_event_handler(): ) # Passing args as strings should format differently. - event_spec = handler("first", "second") # type: ignore + event_spec = handler("first", "second") assert ( format.format_event(event_spec) == 'Event("test_fn_with_args", {arg1:"first",arg2:"second"})' @@ -81,7 +82,7 @@ def test_call_event_handler(): first, second = 123, "456" handler = EventHandler(fn=test_fn_with_args) - event_spec = handler(first, second) # type: ignore + event_spec = handler(first, second) assert ( format.format_event(event_spec) == 'Event("test_fn_with_args", {arg1:123,arg2:"456"})' @@ -95,7 +96,7 @@ def test_call_event_handler(): handler = EventHandler(fn=test_fn_with_args) with pytest.raises(TypeError): - handler(test_fn) # type: ignore + handler(test_fn) def test_call_event_handler_partial(): @@ -446,9 +447,28 @@ def test_event_var_data(): return (value,) # Ensure chain carries _var_data - chain_var = Var.create(EventChain(events=[S.s(S.x)], args_spec=_args_spec)) + chain_var = Var.create( + EventChain( + events=[S.s(S.x)], + args_spec=_args_spec, + invocation=rx.vars.FunctionStringVar.create(""), + ) + ) assert chain_var._get_all_var_data() == S.x._get_all_var_data() + chain_var_data = Var.create( + EventChain( + events=[], + args_spec=_args_spec, + ) + )._get_all_var_data() + assert chain_var_data is not None + + assert chain_var_data == VarData( + imports=Imports.EVENTS, + hooks={Hooks.EVENTS: None}, + ) + def test_event_bound_method() -> None: class S(BaseState): diff --git a/tests/units/test_model.py b/tests/units/test_model.py index 0a83f39ec..b17538248 100644 --- a/tests/units/test_model.py +++ b/tests/units/test_model.py @@ -86,7 +86,7 @@ def test_automigration( assert versions.exists() # initial table - class AlembicThing(Model, table=True): # type: ignore + class AlembicThing(Model, table=True): # pyright: ignore [reportRedeclaration] t1: str with Model.get_db_engine().connect() as connection: @@ -105,7 +105,7 @@ def test_automigration( model_registry.get_metadata().clear() # Create column t2, mark t1 as optional with default - class AlembicThing(Model, table=True): # type: ignore + class AlembicThing(Model, table=True): # pyright: ignore [reportRedeclaration] t1: Optional[str] = "default" t2: str = "bar" @@ -125,7 +125,7 @@ def test_automigration( model_registry.get_metadata().clear() # Drop column t1 - class AlembicThing(Model, table=True): # type: ignore + class AlembicThing(Model, table=True): # pyright: ignore [reportRedeclaration] t2: str = "bar" assert Model.migrate(autogenerate=True) @@ -138,7 +138,7 @@ def test_automigration( assert result[1].t2 == "baz" # Add table - class AlembicSecond(Model, table=True): # type: ignore + class AlembicSecond(Model, table=True): a: int = 42 b: float = 4.2 @@ -160,14 +160,14 @@ def test_automigration( # drop table (AlembicSecond) model_registry.get_metadata().clear() - class AlembicThing(Model, table=True): # type: ignore + class AlembicThing(Model, table=True): # pyright: ignore [reportRedeclaration] t2: str = "bar" assert Model.migrate(autogenerate=True) assert len(list(versions.glob("*.py"))) == 5 with reflex.model.session() as session: - with pytest.raises(sqlalchemy.exc.OperationalError) as errctx: # type: ignore + with pytest.raises(sqlalchemy.exc.OperationalError) as errctx: session.exec(sqlmodel.select(AlembicSecond)).all() assert errctx.match(r"no such table: alembicsecond") # first table should still exist @@ -178,7 +178,7 @@ def test_automigration( model_registry.get_metadata().clear() - class AlembicThing(Model, table=True): # type: ignore + class AlembicThing(Model, table=True): # changing column type not supported by default t2: int = 42 diff --git a/tests/units/test_prerequisites.py b/tests/units/test_prerequisites.py index cf655d6cd..3bd029077 100644 --- a/tests/units/test_prerequisites.py +++ b/tests/units/test_prerequisites.py @@ -100,7 +100,7 @@ def test_transpile_packages(transpile_packages, expected_transpile_packages): transpile_packages=transpile_packages, ) transpile_packages_match = re.search(r"transpilePackages: (\[.*?\])", output) - transpile_packages_json = transpile_packages_match.group(1) # type: ignore + transpile_packages_json = transpile_packages_match.group(1) # pyright: ignore [reportOptionalMemberAccess] actual_transpile_packages = sorted(json.loads(transpile_packages_json)) assert actual_transpile_packages == expected_transpile_packages diff --git a/tests/units/test_state.py b/tests/units/test_state.py index a2b0179d2..1c7e2bf34 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -242,7 +242,7 @@ def test_state() -> TestState: Returns: A test state. """ - return TestState() # type: ignore + return TestState() # pyright: ignore [reportCallIssue] @pytest.fixture @@ -438,7 +438,7 @@ def test_class_indexing_with_vars(): == f"(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))({TestState.get_name()}.array, ...args)))({TestState.get_name()}.num1))" ) - prop = TestState.mapping["a"][TestState.num1] + prop = TestState.mapping["a"][TestState.num1] # pyright: ignore [reportCallIssue, reportArgumentType] assert ( str(prop) == f'(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))({TestState.get_name()}.mapping["a"], ...args)))({TestState.get_name()}.num1))' @@ -558,9 +558,9 @@ def test_get_class_var(): def test_set_class_var(): """Test setting the var of a class.""" with pytest.raises(AttributeError): - TestState.num3 # type: ignore + TestState.num3 # pyright: ignore [reportAttributeAccessIssue] TestState._set_var(Var(_js_expr="num3", _var_type=int)._var_set_state(TestState)) - var = TestState.num3 # type: ignore + var = TestState.num3 # pyright: ignore [reportAttributeAccessIssue] assert var._js_expr == TestState.get_full_name() + ".num3" assert var._var_type is int assert var._var_state == TestState.get_full_name() @@ -793,17 +793,16 @@ async def test_process_event_simple(test_state): assert test_state.num1 == 0 event = Event(token="t", name="set_num1", payload={"value": 69}) - update = await test_state._process(event).__anext__() + async for update in test_state._process(event): + # The event should update the value. + assert test_state.num1 == 69 - # The event should update the value. - assert test_state.num1 == 69 - - # The delta should contain the changes, including computed vars. - assert update.delta == { - TestState.get_full_name(): {"num1": 69, "sum": 72.14}, - GrandchildState3.get_full_name(): {"computed": ""}, - } - assert update.events == [] + # The delta should contain the changes, including computed vars. + assert update.delta == { + TestState.get_full_name(): {"num1": 69, "sum": 72.14}, + GrandchildState3.get_full_name(): {"computed": ""}, + } + assert update.events == [] @pytest.mark.asyncio @@ -823,15 +822,15 @@ async def test_process_event_substate(test_state, child_state, grandchild_state) name=f"{ChildState.get_name()}.change_both", payload={"value": "hi", "count": 12}, ) - update = await test_state._process(event).__anext__() - assert child_state.value == "HI" - assert child_state.count == 24 - assert update.delta == { - # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, - ChildState.get_full_name(): {"value": "HI", "count": 24}, - GrandchildState3.get_full_name(): {"computed": ""}, - } - test_state._clean() + async for update in test_state._process(event): + assert child_state.value == "HI" + assert child_state.count == 24 + assert update.delta == { + # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, + ChildState.get_full_name(): {"value": "HI", "count": 24}, + GrandchildState3.get_full_name(): {"computed": ""}, + } + test_state._clean() # Test with the granchild state. assert grandchild_state.value2 == "" @@ -840,19 +839,19 @@ async def test_process_event_substate(test_state, child_state, grandchild_state) name=f"{GrandchildState.get_full_name()}.set_value2", payload={"value": "new"}, ) - update = await test_state._process(event).__anext__() - assert grandchild_state.value2 == "new" - assert update.delta == { - # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, - GrandchildState.get_full_name(): {"value2": "new"}, - GrandchildState3.get_full_name(): {"computed": ""}, - } + async for update in test_state._process(event): + assert grandchild_state.value2 == "new" + assert update.delta == { + # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, + GrandchildState.get_full_name(): {"value2": "new"}, + GrandchildState3.get_full_name(): {"computed": ""}, + } @pytest.mark.asyncio async def test_process_event_generator(): """Test event handlers that generate multiple updates.""" - gen_state = GenState() # type: ignore + gen_state = GenState() # pyright: ignore [reportCallIssue] event = Event( token="t", name="go", @@ -952,12 +951,12 @@ def test_add_var(): assert not hasattr(ds1, "dynamic_int") ds1.add_var("dynamic_int", int, 42) # Existing instances get the BaseVar - assert ds1.dynamic_int.equals(DynamicState.dynamic_int) # type: ignore + assert ds1.dynamic_int.equals(DynamicState.dynamic_int) # pyright: ignore [reportAttributeAccessIssue] # New instances get an actual value with the default assert DynamicState().dynamic_int == 42 ds1.add_var("dynamic_list", List[int], [5, 10]) - assert ds1.dynamic_list.equals(DynamicState.dynamic_list) # type: ignore + assert ds1.dynamic_list.equals(DynamicState.dynamic_list) # pyright: ignore [reportAttributeAccessIssue] ds2 = DynamicState() assert ds2.dynamic_list == [5, 10] ds2.dynamic_list.append(15) @@ -965,8 +964,8 @@ def test_add_var(): assert DynamicState().dynamic_list == [5, 10] ds1.add_var("dynamic_dict", Dict[str, int], {"k1": 5, "k2": 10}) - assert ds1.dynamic_dict.equals(DynamicState.dynamic_dict) # type: ignore - assert ds2.dynamic_dict.equals(DynamicState.dynamic_dict) # type: ignore + assert ds1.dynamic_dict.equals(DynamicState.dynamic_dict) # pyright: ignore [reportAttributeAccessIssue] + assert ds2.dynamic_dict.equals(DynamicState.dynamic_dict) # pyright: ignore [reportAttributeAccessIssue] assert DynamicState().dynamic_dict == {"k1": 5, "k2": 10} assert DynamicState().dynamic_dict == {"k1": 5, "k2": 10} @@ -1027,7 +1026,7 @@ class InterdependentState(BaseState): Returns: ComputedVar v1x2 multiplied by 2 """ - return self.v1x2 * 2 # type: ignore + return self.v1x2 * 2 @rx.var def _v3(self) -> int: @@ -1274,7 +1273,7 @@ def test_computed_var_cached_depends_on_non_cached(): @rx.var def dep_v(self) -> int: - return self.no_cache_v # type: ignore + return self.no_cache_v @rx.var def comp_v(self) -> int: @@ -1317,7 +1316,7 @@ def test_computed_var_depends_on_parent_non_cached(): class ChildState(ParentState): @rx.var def dep_v(self) -> int: - return self.no_cache_v # type: ignore + return self.no_cache_v ps = ParentState() cs = ps.substates[ChildState.get_name()] @@ -1621,7 +1620,7 @@ async def test_state_with_invalid_yield(capsys, mock_app): id="backend_error", position="top-center", style={"width": "500px"}, - ) # type: ignore + ) # pyright: ignore [reportCallIssue, reportArgumentType] ], token="", ) @@ -1918,7 +1917,7 @@ def mock_app_simple(monkeypatch) -> rx.App: setattr(app_module, CompileVars.APP, app) app._state = TestState - app.event_namespace.emit = CopyingAsyncMock() # type: ignore + app.event_namespace.emit = CopyingAsyncMock() # pyright: ignore [reportOptionalMemberAccess] def _mock_get_app(*args, **kwargs): return app_module @@ -2162,7 +2161,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): """ router_data = {"query": {}} mock_app.state_manager.state = mock_app._state = BackgroundTaskState - async for update in rx.app.process( # type: ignore + async for update in rx.app.process( mock_app, Event( token=token, @@ -2182,7 +2181,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): assert len(mock_app._background_tasks) == 1 # Process another normal event - async for update in rx.app.process( # type: ignore + async for update in rx.app.process( mock_app, Event( token=token, @@ -2293,7 +2292,7 @@ async def test_background_task_reset(mock_app: rx.App, token: str): """ router_data = {"query": {}} mock_app.state_manager.state = mock_app._state = BackgroundTaskState - async for update in rx.app.process( # type: ignore + async for update in rx.app.process( mock_app, Event( token=token, @@ -2635,10 +2634,10 @@ def test_duplicate_substate_class(mocker): class TestState(BaseState): pass - class ChildTestState(TestState): # type: ignore + class ChildTestState(TestState): # pyright: ignore [reportRedeclaration] pass - class ChildTestState(TestState): # type: ignore # noqa + class ChildTestState(TestState): # noqa: F811 pass return TestState @@ -2676,21 +2675,21 @@ def test_reset_with_mutables(): items: List[List[int]] = default instance = MutableResetState() - assert instance.items.__wrapped__ is not default # type: ignore + assert instance.items.__wrapped__ is not default # pyright: ignore [reportAttributeAccessIssue] assert instance.items == default == copied_default instance.items.append([3, 3]) assert instance.items != default assert instance.items != copied_default instance.reset() - assert instance.items.__wrapped__ is not default # type: ignore + assert instance.items.__wrapped__ is not default # pyright: ignore [reportAttributeAccessIssue] assert instance.items == default == copied_default instance.items.append([3, 3]) assert instance.items != default assert instance.items != copied_default instance.reset() - assert instance.items.__wrapped__ is not default # type: ignore + assert instance.items.__wrapped__ is not default # pyright: ignore [reportAttributeAccessIssue] assert instance.items == default == copied_default instance.items.append([3, 3]) assert instance.items != default @@ -2752,30 +2751,30 @@ def test_state_union_optional(): c3r: Custom3 = Custom3(c2r=Custom2(c1r=Custom1(foo=""))) custom_union: Union[Custom1, Custom2, Custom3] = Custom1(foo="") - assert str(UnionState.c3.c2) == f'{UnionState.c3!s}?.["c2"]' # type: ignore - assert str(UnionState.c3.c2.c1) == f'{UnionState.c3!s}?.["c2"]?.["c1"]' # type: ignore + assert str(UnionState.c3.c2) == f'{UnionState.c3!s}?.["c2"]' # pyright: ignore [reportOptionalMemberAccess] + assert str(UnionState.c3.c2.c1) == f'{UnionState.c3!s}?.["c2"]?.["c1"]' # pyright: ignore [reportOptionalMemberAccess] assert ( - str(UnionState.c3.c2.c1.foo) == f'{UnionState.c3!s}?.["c2"]?.["c1"]?.["foo"]' # type: ignore + str(UnionState.c3.c2.c1.foo) == f'{UnionState.c3!s}?.["c2"]?.["c1"]?.["foo"]' # pyright: ignore [reportOptionalMemberAccess] ) assert ( - str(UnionState.c3.c2.c1r.foo) == f'{UnionState.c3!s}?.["c2"]?.["c1r"]["foo"]' # type: ignore + str(UnionState.c3.c2.c1r.foo) == f'{UnionState.c3!s}?.["c2"]?.["c1r"]["foo"]' # pyright: ignore [reportOptionalMemberAccess] ) - assert str(UnionState.c3.c2r.c1) == f'{UnionState.c3!s}?.["c2r"]["c1"]' # type: ignore + assert str(UnionState.c3.c2r.c1) == f'{UnionState.c3!s}?.["c2r"]["c1"]' # pyright: ignore [reportOptionalMemberAccess] assert ( - str(UnionState.c3.c2r.c1.foo) == f'{UnionState.c3!s}?.["c2r"]["c1"]?.["foo"]' # type: ignore + str(UnionState.c3.c2r.c1.foo) == f'{UnionState.c3!s}?.["c2r"]["c1"]?.["foo"]' # pyright: ignore [reportOptionalMemberAccess] ) assert ( - str(UnionState.c3.c2r.c1r.foo) == f'{UnionState.c3!s}?.["c2r"]["c1r"]["foo"]' # type: ignore + str(UnionState.c3.c2r.c1r.foo) == f'{UnionState.c3!s}?.["c2r"]["c1r"]["foo"]' # pyright: ignore [reportOptionalMemberAccess] ) - assert str(UnionState.c3i.c2) == f'{UnionState.c3i!s}["c2"]' # type: ignore - assert str(UnionState.c3r.c2) == f'{UnionState.c3r!s}["c2"]' # type: ignore - assert UnionState.custom_union.foo is not None # type: ignore - assert UnionState.custom_union.c1 is not None # type: ignore - assert UnionState.custom_union.c1r is not None # type: ignore - assert UnionState.custom_union.c2 is not None # type: ignore - assert UnionState.custom_union.c2r is not None # type: ignore - assert types.is_optional(UnionState.opt_int._var_type) # type: ignore - assert types.is_union(UnionState.int_float._var_type) # type: ignore + assert str(UnionState.c3i.c2) == f'{UnionState.c3i!s}["c2"]' + assert str(UnionState.c3r.c2) == f'{UnionState.c3r!s}["c2"]' + assert UnionState.custom_union.foo is not None # pyright: ignore [reportAttributeAccessIssue] + assert UnionState.custom_union.c1 is not None # pyright: ignore [reportAttributeAccessIssue] + assert UnionState.custom_union.c1r is not None # pyright: ignore [reportAttributeAccessIssue] + assert UnionState.custom_union.c2 is not None # pyright: ignore [reportAttributeAccessIssue] + assert UnionState.custom_union.c2r is not None # pyright: ignore [reportAttributeAccessIssue] + assert types.is_optional(UnionState.opt_int._var_type) # pyright: ignore [reportAttributeAccessIssue, reportOptionalMemberAccess] + assert types.is_union(UnionState.int_float._var_type) # pyright: ignore [reportAttributeAccessIssue] def test_set_base_field_via_setter(): @@ -2921,10 +2920,10 @@ async def test_preprocess(app_module_mock, token, test_state, expected, mocker): events = updates[0].events assert len(events) == 2 - assert (await state._process(events[0]).__anext__()).delta == { - test_state.get_full_name(): {"num": 1} - } - assert (await state._process(events[1]).__anext__()).delta == exp_is_hydrated(state) + async for update in state._process(events[0]): + assert update.delta == {test_state.get_full_name(): {"num": 1}} + async for update in state._process(events[1]): + assert update.delta == exp_is_hydrated(state) if isinstance(app.state_manager, StateManagerRedis): await app.state_manager.close() @@ -2969,13 +2968,12 @@ async def test_preprocess_multiple_load_events(app_module_mock, token, mocker): events = updates[0].events assert len(events) == 3 - assert (await state._process(events[0]).__anext__()).delta == { - OnLoadState.get_full_name(): {"num": 1} - } - assert (await state._process(events[1]).__anext__()).delta == { - OnLoadState.get_full_name(): {"num": 2} - } - assert (await state._process(events[2]).__anext__()).delta == exp_is_hydrated(state) + async for update in state._process(events[0]): + assert update.delta == {OnLoadState.get_full_name(): {"num": 1}} + async for update in state._process(events[1]): + assert update.delta == {OnLoadState.get_full_name(): {"num": 2}} + async for update in state._process(events[2]): + assert update.delta == exp_is_hydrated(state) if isinstance(app.state_manager, StateManagerRedis): await app.state_manager.close() @@ -3372,9 +3370,9 @@ config = rx.Config( from reflex.state import State, StateManager state_manager = StateManager.create(state=State) - assert state_manager.lock_expiration == expected_values[0] # type: ignore - assert state_manager.token_expiration == expected_values[1] # type: ignore - assert state_manager.lock_warning_threshold == expected_values[2] # type: ignore + assert state_manager.lock_expiration == expected_values[0] # pyright: ignore [reportAttributeAccessIssue] + assert state_manager.token_expiration == expected_values[1] # pyright: ignore [reportAttributeAccessIssue] + assert state_manager.lock_warning_threshold == expected_values[2] # pyright: ignore [reportAttributeAccessIssue] @pytest.mark.skipif("REDIS_URL" not in os.environ, reason="Test requires redis") @@ -3453,7 +3451,7 @@ def test_mixin_state() -> None: assert "computed" in UsesMixinState.vars assert ( - UsesMixinState(_reflex_internal_init=True)._backend_no_default # type: ignore + UsesMixinState(_reflex_internal_init=True)._backend_no_default # pyright: ignore [reportCallIssue] is not UsesMixinState.backend_vars["_backend_no_default"] ) @@ -3473,7 +3471,7 @@ def test_assignment_to_undeclared_vars(): class State(BaseState): val: str _val: str - __val: str # type: ignore + __val: str # pyright: ignore [reportGeneralTypeIssues] def handle_supported_regular_vars(self): self.val = "no underscore" @@ -3493,8 +3491,8 @@ def test_assignment_to_undeclared_vars(): def handle_var(self): self.value = 20 - state = State() # type: ignore - sub_state = Substate() # type: ignore + state = State() # pyright: ignore [reportCallIssue] + sub_state = Substate() # pyright: ignore [reportCallIssue] with pytest.raises(SetUndefinedStateVarError): state.handle_regular_var() @@ -3556,7 +3554,7 @@ def test_fallback_pickle(): _f: Optional[Callable] = None _g: Any = None - state = DillState(_reflex_internal_init=True) # type: ignore + state = DillState(_reflex_internal_init=True) # pyright: ignore [reportCallIssue] state._o = Obj(_f=lambda: 42) state._f = lambda: 420 @@ -3567,14 +3565,14 @@ def test_fallback_pickle(): assert unpickled_state._o._f() == 42 # Threading locks are unpicklable normally, and raise TypeError instead of PicklingError. - state2 = DillState(_reflex_internal_init=True) # type: ignore + state2 = DillState(_reflex_internal_init=True) # pyright: ignore [reportCallIssue] state2._g = threading.Lock() pk2 = state2._serialize() unpickled_state2 = BaseState._deserialize(pk2) assert isinstance(unpickled_state2._g, type(threading.Lock())) # Some object, like generator, are still unpicklable with dill. - state3 = DillState(_reflex_internal_init=True) # type: ignore + state3 = DillState(_reflex_internal_init=True) # pyright: ignore [reportCallIssue] state3._g = (i for i in range(10)) with pytest.raises(StateSerializationError): @@ -3749,7 +3747,7 @@ class UpcastState(rx.State): assert isinstance(a, list) self.passed = True - def py_unresolvable(self, u: "Unresolvable"): # noqa: D102, F821 # type: ignore + def py_unresolvable(self, u: "Unresolvable"): # noqa: D102, F821 # pyright: ignore [reportUndefinedVariable] assert isinstance(u, list) self.passed = True diff --git a/tests/units/test_style.py b/tests/units/test_style.py index bb585fd22..e8ff5bd01 100644 --- a/tests/units/test_style.py +++ b/tests/units/test_style.py @@ -356,7 +356,7 @@ def test_style_via_component( style_dict: The style_dict to pass to the component. expected_get_style: The expected style dict. """ - comp = rx.el.div(style=style_dict, **kwargs) # type: ignore + comp = rx.el.div(style=style_dict, **kwargs) # pyright: ignore [reportArgumentType] compare_dict_of_var(comp._get_style(), expected_get_style) @@ -515,17 +515,17 @@ def test_evaluate_style_namespaces(): """Test that namespaces get converted to component create functions.""" style_dict = {rx.text: {"color": "blue"}} assert rx.text.__call__ not in style_dict - style_dict = evaluate_style_namespaces(style_dict) # type: ignore + style_dict = evaluate_style_namespaces(style_dict) # pyright: ignore [reportArgumentType] 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 = LiteralVar.create("red")._replace( - merge_var_data=VarData(hooks={"const red = true": None}), # type: ignore + merge_var_data=VarData(hooks={"const red = true": None}), ) blue_var = LiteralVar.create("blue")._replace( - merge_var_data=VarData(hooks={"const blue = true": None}), # type: ignore + merge_var_data=VarData(hooks={"const blue = true": None}), ) s1 = Style( @@ -541,3 +541,7 @@ def test_style_update_with_var_data(): 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 + + s3 = s1 | s2 + assert s3._var_data is not None + assert "_varData" not in s3 diff --git a/tests/units/test_var.py b/tests/units/test_var.py index 4d2ffc8d3..84da0d9a0 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -1,6 +1,5 @@ import json import math -import sys import typing from typing import Dict, List, Mapping, Optional, Set, Tuple, Union, cast @@ -188,6 +187,7 @@ def ChildWithRuntimeOnlyVar(StateWithRuntimeOnlyVar): "state.local", "local2", ], + strict=True, ), ) def test_full_name(prop, expected): @@ -205,6 +205,7 @@ def test_full_name(prop, expected): zip( test_vars, ["prop1", "key", "state.value", "state.local", "local2"], + strict=True, ), ) def test_str(prop, expected): @@ -251,6 +252,7 @@ def test_default_value(prop: Var, expected): "state.set_local", "set_local2", ], + strict=True, ), ) def test_get_setter(prop: Var, expected): @@ -285,7 +287,7 @@ def test_create(value, expected): expected: The expected name of the setter function. """ prop = LiteralVar.create(value) - assert prop.equals(expected) # type: ignore + assert prop.equals(expected) def test_create_type_error(): @@ -446,19 +448,13 @@ class Bar(rx.Base): @pytest.mark.parametrize( ("var", "var_type"), - ( - [ - (Var(_js_expr="", _var_type=Foo | Bar).guess_type(), Foo | Bar), - (Var(_js_expr="", _var_type=Foo | Bar).guess_type().bar, Union[int, str]), - ] - if sys.version_info >= (3, 10) - else [] - ) - + [ - (Var(_js_expr="", _var_type=Union[Foo, Bar]).guess_type(), Union[Foo, Bar]), - (Var(_js_expr="", _var_type=Union[Foo, Bar]).guess_type().baz, str), + [ + (Var(_js_expr="").to(Foo | Bar), Foo | Bar), + (Var(_js_expr="").to(Foo | Bar).bar, Union[int, str]), + (Var(_js_expr="").to(Union[Foo, Bar]), Union[Foo, Bar]), + (Var(_js_expr="").to(Union[Foo, Bar]).baz, str), ( - Var(_js_expr="", _var_type=Union[Foo, Bar]).guess_type().foo, + Var(_js_expr="").to(Union[Foo, Bar]).foo, Union[int, None], ), ], @@ -1241,7 +1237,7 @@ def test_var_component(): for _, imported_objects in var_data.imports ) - has_eval_react_component(ComponentVarState.field_var) # type: ignore + has_eval_react_component(ComponentVarState.field_var) # pyright: ignore [reportArgumentType] has_eval_react_component(ComponentVarState.computed_var) @@ -1253,15 +1249,15 @@ def test_type_chains(): List[int], ) assert ( - str(object_var.keys()[0].upper()) # type: ignore + str(object_var.keys()[0].upper()) == '(((...args) => (((_string) => String.prototype.toUpperCase.apply(_string))((((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))((Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))), ...args)))(0)), ...args)))())' ) assert ( - str(object_var.entries()[1][1] - 1) # type: ignore + str(object_var.entries()[1][1] - 1) == '((((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))((((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))((Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))), ...args)))(1)), ...args)))(1)) - 1)' ) assert ( - str(object_var["c"] + object_var["b"]) # type: ignore + str(object_var["c"] + object_var["b"]) # pyright: ignore [reportCallIssue, reportOperatorIssue] == '(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["c"] + ({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["b"])' ) @@ -1274,7 +1270,7 @@ def test_nested_dict(): assert ( str(first_bar_element) - == '(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))((((...args) => (((_array, _index) => _array.at(_index))([({ ["bar"] : ["foo", "bar"] })], ...args)))(0))["bar"], ...args)))(0))' + == '(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))((((...args) => (((_array, _index) => _array.at(_index))([({ ["bar"] : ["foo", "bar"] })], ...args)))(0))["bar"], ...args)))(0))' # pyright: ignore [reportIndexIssue] ) @@ -1470,7 +1466,7 @@ def test_unsupported_types_for_contains(var: Var): var: The base var. """ with pytest.raises(TypeError) as err: - assert var.contains(1) + assert var.contains(1) # pyright: ignore [reportAttributeAccessIssue] assert ( err.value.args[0] == f"Var of type {var._var_type} does not support contains check." @@ -1500,7 +1496,7 @@ def test_unsupported_types_for_string_contains(other): def test_unsupported_default_contains(): with pytest.raises(TypeError) as err: - assert 1 in Var(_js_expr="var", _var_type=str).guess_type() + assert 1 in Var(_js_expr="var", _var_type=str).guess_type() # pyright: ignore [reportOperatorIssue] assert ( err.value.args[0] == "'in' operator not supported for Var types, use Var.contains() instead." @@ -1969,3 +1965,19 @@ def test_to_string_operation(): single_var = Var.create(Email()) assert single_var._var_type == Email + + +def test_var_data_hooks(): + var_data_str = VarData(hooks="what") + var_data_list = VarData(hooks=["what"]) + var_data_dict = VarData(hooks={"what": None}) + assert var_data_str == var_data_list == var_data_dict + + var_data_list_multiple = VarData(hooks=["what", "whot"]) + var_data_dict_multiple = VarData(hooks={"what": None, "whot": None}) + assert var_data_list_multiple == var_data_dict_multiple + + +def test_var_data_with_hooks_value(): + var_data = VarData(hooks={"what": VarData(hooks={"whot": VarData(hooks="whott")})}) + assert var_data == VarData(hooks=["what", "whot", "whott"]) diff --git a/tests/units/utils/test_format.py b/tests/units/utils/test_format.py index 48281a119..500e6341f 100644 --- a/tests/units/utils/test_format.py +++ b/tests/units/utils/test_format.py @@ -411,7 +411,7 @@ def test_format_event_handler(input, output): input: The event handler input. output: The expected output. """ - assert format.format_event_handler(input) == output # type: ignore + assert format.format_event_handler(input) == output @pytest.mark.parametrize( @@ -470,7 +470,7 @@ formatted_router = { "input, output", [ ( - TestState(_reflex_internal_init=True).dict(), # type: ignore + TestState(_reflex_internal_init=True).dict(), # pyright: ignore [reportCallIssue] { TestState.get_full_name(): { "array": [1, 2, 3.14], @@ -503,7 +503,7 @@ formatted_router = { }, ), ( - DateTimeState(_reflex_internal_init=True).dict(), # type: ignore + DateTimeState(_reflex_internal_init=True).dict(), # pyright: ignore [reportCallIssue] { DateTimeState.get_full_name(): { "d": "1989-11-09", diff --git a/tests/units/utils/test_utils.py b/tests/units/utils/test_utils.py index 37c92c463..fab6f897f 100644 --- a/tests/units/utils/test_utils.py +++ b/tests/units/utils/test_utils.py @@ -31,7 +31,7 @@ def get_above_max_version(): """ semantic_version_list = constants.Bun.VERSION.split(".") - semantic_version_list[-1] = str(int(semantic_version_list[-1]) + 1) # type: ignore + semantic_version_list[-1] = str(int(semantic_version_list[-1]) + 1) # pyright: ignore [reportArgumentType, reportCallIssue] return ".".join(semantic_version_list) @@ -132,13 +132,13 @@ def test_validate_invalid_bun_path(mocker): Args: mocker: Pytest mocker object. """ - mock = mocker.Mock() - mocker.patch.object(mock, "bun_path", return_value="/mock/path") - mocker.patch("reflex.utils.prerequisites.get_config", mock) + mock_path = mocker.Mock() + mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=mock_path) mocker.patch("reflex.utils.prerequisites.get_bun_version", return_value=None) with pytest.raises(typer.Exit): prerequisites.validate_bun() + mock_path.samefile.assert_called_once() def test_validate_bun_path_incompatible_version(mocker): @@ -147,9 +147,8 @@ def test_validate_bun_path_incompatible_version(mocker): Args: mocker: Pytest mocker object. """ - mock = mocker.Mock() - mocker.patch.object(mock, "bun_path", return_value="/mock/path") - mocker.patch("reflex.utils.prerequisites.get_config", mock) + mock_path = mocker.Mock() + mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=mock_path) mocker.patch( "reflex.utils.prerequisites.get_bun_version", return_value=version.parse("0.6.5"), @@ -597,9 +596,7 @@ def test_style_prop_with_event_handler_value(callable): } with pytest.raises(ReflexError): - rx.box( - style=style, # type: ignore - ) + rx.box(style=style) # pyright: ignore [reportArgumentType] def test_is_prod_mode() -> None: