Merge branch 'main' into lendemor/add_N_rules
This commit is contained in:
commit
f2f810ffc5
14
.github/workflows/benchmarks.yml
vendored
14
.github/workflows/benchmarks.yml
vendored
@ -81,24 +81,18 @@ jobs:
|
||||
matrix:
|
||||
# Show OS combos first in GUI
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8"]
|
||||
python-version: ['3.10.16', '3.11.11', '3.12.8']
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
python-version: "3.10.16"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.21"
|
||||
python-version: '3.10.16'
|
||||
# keep only one python version for MacOS
|
||||
- os: macos-latest
|
||||
python-version: "3.9.21"
|
||||
- os: macos-latest
|
||||
python-version: "3.10.16"
|
||||
python-version: '3.10.16'
|
||||
- os: macos-latest
|
||||
python-version: "3.11.11"
|
||||
include:
|
||||
- os: windows-latest
|
||||
python-version: "3.10.11"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.13"
|
||||
python-version: '3.10.11'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/setup_build_env
|
||||
with:
|
||||
python-version: "3.9.21"
|
||||
python-version: '3.10'
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
|
||||
|
@ -47,14 +47,14 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
run-poetry-install: true
|
||||
create-venv-at-path: .venv
|
||||
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split
|
||||
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
|
||||
- name: Run app harness tests
|
||||
env:
|
||||
SCREENSHOT_DIR: /tmp/screenshots/${{ matrix.state_manager }}/${{ matrix.python-version }}/${{ matrix.split_index }}
|
||||
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
|
||||
run: |
|
||||
poetry run playwright install chromium
|
||||
poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}
|
||||
poetry run pytest tests/integration --retries 3 --maxfail=5 --splits 2 --group ${{matrix.split_index}}
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload failed test screenshots
|
||||
if: always()
|
||||
|
11
.github/workflows/integration_tests.yml
vendored
11
.github/workflows/integration_tests.yml
vendored
@ -43,22 +43,17 @@ jobs:
|
||||
matrix:
|
||||
# Show OS combos first in GUI
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
||||
# Windows is a bit behind on Python version availability in Github
|
||||
python-version: ['3.10.16', '3.11.11', '3.12.8', '3.13.1']
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
python-version: "3.11.11"
|
||||
- os: windows-latest
|
||||
python-version: "3.10.16"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.21"
|
||||
python-version: '3.10.16'
|
||||
include:
|
||||
- os: windows-latest
|
||||
python-version: "3.11.9"
|
||||
- os: windows-latest
|
||||
python-version: "3.10.11"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.13"
|
||||
python-version: '3.10.11'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
10
.github/workflows/unit_tests.yml
vendored
10
.github/workflows/unit_tests.yml
vendored
@ -28,22 +28,18 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
||||
python-version: ["3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
||||
# Windows is a bit behind on Python version availability in Github
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
python-version: "3.11.11"
|
||||
- os: windows-latest
|
||||
python-version: "3.10.16"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.21"
|
||||
include:
|
||||
- os: windows-latest
|
||||
python-version: "3.11.9"
|
||||
- os: windows-latest
|
||||
python-version: "3.10.11"
|
||||
- os: windows-latest
|
||||
python-version: "3.9.13"
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# Service containers to run with `runner-job`
|
||||
@ -92,8 +88,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Note: py39, py310, py311 versions chosen due to available arm64 darwin builds.
|
||||
python-version: ["3.9.13", "3.10.11", "3.11.9", "3.12.8", "3.13.1"]
|
||||
# Note: py310, py311 versions chosen due to available arm64 darwin builds.
|
||||
python-version: ["3.10.11", "3.11.9", "3.12.8", "3.13.1"]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -28,7 +28,7 @@ repos:
|
||||
entry: python3 scripts/make_pyi.py
|
||||
|
||||
- repo: https://github.com/RobertCraigie/pyright-python
|
||||
rev: v1.1.313
|
||||
rev: v1.1.334
|
||||
hooks:
|
||||
- id: pyright
|
||||
args: [reflex, tests]
|
||||
|
@ -8,7 +8,7 @@ Here is a quick guide on how to run Reflex repo locally so you can start contrib
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
- Python >= 3.9
|
||||
- Python >= 3.10
|
||||
- Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
|
||||
|
||||
**1. Fork this repository:**
|
||||
@ -87,7 +87,7 @@ poetry run ruff format .
|
||||
```
|
||||
|
||||
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit.
|
||||
Note that pre-commit will only be installed when you use a Python version >= 3.9.
|
||||
Note that pre-commit will only be installed when you use a Python version >= 3.10.
|
||||
|
||||
``` bash
|
||||
pre-commit install
|
||||
|
@ -34,7 +34,7 @@ See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architectu
|
||||
|
||||
## ⚙️ Installation
|
||||
|
||||
Open a terminal and run (Requires Python 3.9+):
|
||||
Open a terminal and run (Requires Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -34,7 +34,7 @@ Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-archit
|
||||
|
||||
## ⚙️ Installation
|
||||
|
||||
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.9+):
|
||||
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -35,7 +35,7 @@ Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-re
|
||||
|
||||
## ⚙️ Instalación
|
||||
|
||||
Abra un terminal y ejecute (Requiere Python 3.9+):
|
||||
Abra un terminal y ejecute (Requiere Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -35,7 +35,7 @@ Reflex के अंदर के कामकाज को जानने क
|
||||
|
||||
## ⚙️ इंस्टॉलेशन (Installation)
|
||||
|
||||
एक टर्मिनल खोलें और चलाएं (Python 3.9+ की आवश्यकता है):
|
||||
एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
## ⚙️ Installazione
|
||||
|
||||
Apri un terminale ed esegui (Richiede Python 3.9+):
|
||||
Apri un terminale ed esegui (Richiede Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -37,7 +37,7 @@ Reflex がどのように動作しているかを知るには、[アーキテク
|
||||
|
||||
## ⚙️ インストール
|
||||
|
||||
ターミナルを開いて以下のコマンドを実行してください。(Python 3.9 以上が必要です。):
|
||||
ターミナルを開いて以下のコマンドを実行してください。(Python 3.10 以上が必要です。):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -20,7 +20,7 @@
|
||||
---
|
||||
## ⚙️ 설치
|
||||
|
||||
터미널을 열고 실행하세요. (Python 3.9+ 필요):
|
||||
터미널을 열고 실행하세요. (Python 3.10+ 필요):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -34,7 +34,7 @@
|
||||
|
||||
## ⚙️ Installation - نصب و راه اندازی
|
||||
|
||||
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.9+):
|
||||
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -21,7 +21,7 @@
|
||||
---
|
||||
## ⚙️ Instalação
|
||||
|
||||
Abra um terminal e execute (Requer Python 3.9+):
|
||||
Abra um terminal e execute (Requer Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
## ⚙️ Kurulum
|
||||
|
||||
Bir terminal açın ve çalıştırın (Python 3.9+ gerekir):
|
||||
Bir terminal açın ve çalıştırın (Python 3.10+ gerekir):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -34,7 +34,7 @@ Các tính năng chính:
|
||||
|
||||
## ⚙️ Cài đặt
|
||||
|
||||
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.9+):
|
||||
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -34,7 +34,7 @@ Reflex 是一个使用纯Python构建全栈web应用的库。
|
||||
|
||||
## ⚙️ 安装
|
||||
|
||||
打开一个终端并且运行(要求Python3.9+):
|
||||
打开一个终端并且运行(要求Python3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
@ -36,7 +36,7 @@ Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫
|
||||
|
||||
## ⚙️ 安裝
|
||||
|
||||
開啟一個終端機並且執行 (需要 Python 3.9+):
|
||||
開啟一個終端機並且執行 (需要 Python 3.10+):
|
||||
|
||||
```bash
|
||||
pip install reflex
|
||||
|
1437
poetry.lock
generated
1437
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,7 @@ keywords = ["web", "framework"]
|
||||
classifiers = ["Development Status :: 4 - Beta"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
python = "^3.10"
|
||||
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
|
||||
gunicorn = ">=20.1.0,<24.0"
|
||||
jinja2 = ">=3.1.2,<4.0"
|
||||
@ -50,7 +50,6 @@ httpx = ">=0.25.1,<1.0"
|
||||
twine = ">=4.0.0,<7.0"
|
||||
tomlkit = ">=0.12.4,<1.0"
|
||||
lazy_loader = ">=0.4"
|
||||
reflex-chakra = ">=0.6.0"
|
||||
typing_extensions = ">=4.6.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
@ -105,5 +104,5 @@ asyncio_default_fixture_loop_scope = "function"
|
||||
asyncio_mode = "auto"
|
||||
|
||||
[tool.codespell]
|
||||
skip = "docs/*,*.html,examples/*, *.pyi"
|
||||
skip = "docs/*,*.html,examples/*, *.pyi, poetry.lock"
|
||||
ignore-words-list = "te, TreeE"
|
||||
|
@ -8,7 +8,7 @@ version = "0.0.1"
|
||||
description = "Reflex custom component {{ module_name }}"
|
||||
readme = "README.md"
|
||||
license = { text = "Apache-2.0" }
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
authors = [{ name = "", email = "YOUREMAIL@domain.com" }]
|
||||
keywords = ["reflex","reflex-custom-components"]
|
||||
|
||||
|
@ -3,6 +3,7 @@ import axios from "axios";
|
||||
import io from "socket.io-client";
|
||||
import JSON5 from "json5";
|
||||
import env from "$/env.json";
|
||||
import reflexEnvironment from "$/reflex.json";
|
||||
import Cookies from "universal-cookie";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Router, { useRouter } from "next/router";
|
||||
@ -407,6 +408,7 @@ export const connect = async (
|
||||
socket.current = io(endpoint.href, {
|
||||
path: endpoint["pathname"],
|
||||
transports: transports,
|
||||
protocols: env.TEST_MODE ? undefined : [reflexEnvironment.version],
|
||||
autoUnref: false,
|
||||
});
|
||||
// Ensure undefined fields in events are sent as null instead of removed
|
||||
|
@ -303,7 +303,6 @@ _MAPPING: dict = {
|
||||
"event": [
|
||||
"EventChain",
|
||||
"EventHandler",
|
||||
"background",
|
||||
"call_script",
|
||||
"call_function",
|
||||
"run_script",
|
||||
@ -367,19 +366,4 @@ getattr, __dir__, __all__ = lazy_loader.attach(
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "chakra":
|
||||
from reflex.utils import console
|
||||
|
||||
console.deprecate(
|
||||
"rx.chakra",
|
||||
reason="and moved to a separate package. "
|
||||
"To continue using Chakra UI components, install the `reflex-chakra` package via `pip install "
|
||||
"reflex-chakra`.",
|
||||
deprecation_version="0.6.0",
|
||||
removal_version="0.7.0",
|
||||
dedupe=True,
|
||||
)
|
||||
import reflex_chakra as rc
|
||||
|
||||
return rc
|
||||
return getattr(name)
|
||||
|
@ -156,7 +156,6 @@ from .constants import Env as Env
|
||||
from .constants.colors import Color as Color
|
||||
from .event import EventChain as EventChain
|
||||
from .event import EventHandler as EventHandler
|
||||
from .event import background as background
|
||||
from .event import call_function as call_function
|
||||
from .event import call_script as call_script
|
||||
from .event import clear_local_storage as clear_local_storage
|
||||
|
@ -558,11 +558,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
def _compile_page(self, route: str):
|
||||
def _compile_page(self, route: str, save_page: bool = True):
|
||||
"""Compile a page.
|
||||
|
||||
Args:
|
||||
route: The route of the page to compile.
|
||||
save_page: If True, the compiled page is saved to self.pages.
|
||||
"""
|
||||
component, enable_state = compiler.compile_unevaluated_page(
|
||||
route, self.unevaluated_pages[route], self.state, self.style, self.theme
|
||||
@ -573,7 +574,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
|
||||
# Add the page.
|
||||
self._check_routes_conflict(route)
|
||||
self.pages[route] = component
|
||||
if save_page:
|
||||
self.pages[route] = component
|
||||
|
||||
def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
|
||||
"""Get the load events for a route.
|
||||
@ -873,14 +875,16 @@ class App(MiddlewareMixin, LifespanMixin):
|
||||
# If a theme component was provided, wrap the app with it
|
||||
app_wrappers[(20, "Theme")] = self.theme
|
||||
|
||||
should_compile = self._should_compile()
|
||||
|
||||
for route in self.unevaluated_pages:
|
||||
console.debug(f"Evaluating page: {route}")
|
||||
self._compile_page(route)
|
||||
self._compile_page(route, save_page=should_compile)
|
||||
|
||||
# Add the optional endpoints (_upload)
|
||||
self._add_optional_endpoints()
|
||||
|
||||
if not self._should_compile():
|
||||
if not should_compile:
|
||||
return
|
||||
|
||||
self._validate_var_dependencies()
|
||||
@ -1524,7 +1528,11 @@ class EventNamespace(AsyncNamespace):
|
||||
sid: The Socket.IO session id.
|
||||
environ: The request information, including HTTP headers.
|
||||
"""
|
||||
pass
|
||||
subprotocol = environ.get("HTTP_SEC_WEBSOCKET_PROTOCOL", None)
|
||||
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):
|
||||
"""Event for when the websocket disconnects.
|
||||
|
@ -23,8 +23,6 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from typing_extensions import deprecated
|
||||
|
||||
import reflex.state
|
||||
from reflex.base import Base
|
||||
from reflex.compiler.templates import STATEFUL_COMPONENT
|
||||
@ -47,11 +45,10 @@ from reflex.event import (
|
||||
EventChain,
|
||||
EventHandler,
|
||||
EventSpec,
|
||||
EventVar,
|
||||
no_args_event_spec,
|
||||
)
|
||||
from reflex.style import Style, format_as_emotion
|
||||
from reflex.utils import console, format, imports, types
|
||||
from reflex.utils import format, imports, types
|
||||
from reflex.utils.imports import (
|
||||
ImmutableParsedImportDict,
|
||||
ImportDict,
|
||||
@ -547,41 +544,6 @@ class Component(BaseComponent, ABC):
|
||||
# Construct the component.
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@deprecated("Use rx.EventChain.create instead.")
|
||||
def _create_event_chain(
|
||||
self,
|
||||
args_spec: types.ArgsSpec | Sequence[types.ArgsSpec],
|
||||
value: Union[
|
||||
Var,
|
||||
EventHandler,
|
||||
EventSpec,
|
||||
List[Union[EventHandler, EventSpec, EventVar]],
|
||||
Callable,
|
||||
],
|
||||
key: Optional[str] = None,
|
||||
) -> Union[EventChain, Var]:
|
||||
"""Create an event chain from a variety of input types.
|
||||
|
||||
Args:
|
||||
args_spec: The args_spec of the event trigger being bound.
|
||||
value: The value to create the event chain from.
|
||||
key: The key of the event trigger being bound.
|
||||
|
||||
Returns:
|
||||
The event chain.
|
||||
"""
|
||||
console.deprecate(
|
||||
"Component._create_event_chain",
|
||||
"Use rx.EventChain.create instead.",
|
||||
deprecation_version="0.6.8",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
return EventChain.create(
|
||||
value=value, # type: ignore
|
||||
args_spec=args_spec,
|
||||
key=key,
|
||||
)
|
||||
|
||||
def get_event_triggers(
|
||||
self,
|
||||
) -> Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]]:
|
||||
|
@ -14,7 +14,7 @@ from reflex.components.radix.themes.layout.box import Box
|
||||
from reflex.constants.colors import Color
|
||||
from reflex.event import set_clipboard
|
||||
from reflex.style import Style
|
||||
from reflex.utils import console, format
|
||||
from reflex.utils import format
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars.base import LiteralVar, Var, VarData
|
||||
|
||||
@ -438,6 +438,8 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
can_copy = props.pop("can_copy", False)
|
||||
copy_button = props.pop("copy_button", None)
|
||||
|
||||
# react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
|
||||
# themes respectively to ensure code compatibility.
|
||||
if "theme" not in props:
|
||||
# Default color scheme responds to global color mode.
|
||||
props["theme"] = color_mode_cond(
|
||||
@ -445,17 +447,6 @@ class CodeBlock(Component, MarkdownComponentMap):
|
||||
dark=Theme.one_dark,
|
||||
)
|
||||
|
||||
# react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
|
||||
# themes respectively to ensure code compatibility.
|
||||
if "theme" in props and not isinstance(props["theme"], Var):
|
||||
props["theme"] = getattr(Theme, format.to_snake_case(props["theme"])) # type: ignore
|
||||
console.deprecate(
|
||||
feature_name="theme prop as string",
|
||||
reason="Use code_block.themes instead.",
|
||||
deprecation_version="0.6.0",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
|
||||
if can_copy:
|
||||
code = children[0]
|
||||
copy_button = ( # type: ignore
|
||||
|
@ -102,7 +102,7 @@ class Fieldset(Element):
|
||||
name: Var[Union[str, int, bool]]
|
||||
|
||||
|
||||
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
|
||||
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]:
|
||||
"""Event handler spec for the on_submit event.
|
||||
|
||||
Returns:
|
||||
@ -111,7 +111,7 @@ def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
|
||||
return (FORM_DATA,)
|
||||
|
||||
|
||||
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]:
|
||||
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]:
|
||||
"""Event handler spec for the on_submit event.
|
||||
|
||||
Returns:
|
||||
|
@ -270,8 +270,8 @@ class Fieldset(Element):
|
||||
"""
|
||||
...
|
||||
|
||||
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]: ...
|
||||
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]: ...
|
||||
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]: ...
|
||||
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]: ...
|
||||
|
||||
class Form(BaseHTML):
|
||||
@overload
|
||||
@ -341,10 +341,10 @@ class Form(BaseHTML):
|
||||
on_submit: Optional[
|
||||
Union[
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||
],
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||
],
|
||||
]
|
||||
] = None,
|
||||
|
@ -132,10 +132,10 @@ class FormRoot(FormComponent, HTMLForm):
|
||||
on_submit: Optional[
|
||||
Union[
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||
],
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||
],
|
||||
]
|
||||
] = None,
|
||||
@ -608,10 +608,10 @@ class Form(FormRoot):
|
||||
on_submit: Optional[
|
||||
Union[
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||
],
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||
],
|
||||
]
|
||||
] = None,
|
||||
@ -741,10 +741,10 @@ class FormNamespace(ComponentNamespace):
|
||||
on_submit: Optional[
|
||||
Union[
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||
],
|
||||
Union[
|
||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
||||
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||
],
|
||||
]
|
||||
] = None,
|
||||
|
@ -70,6 +70,8 @@ _SUBMOD_ATTRS: dict = {
|
||||
"Label",
|
||||
"label_list",
|
||||
"LabelList",
|
||||
"cell",
|
||||
"Cell",
|
||||
],
|
||||
"polar": [
|
||||
"pie",
|
||||
|
@ -53,11 +53,13 @@ from .charts import radar_chart as radar_chart
|
||||
from .charts import radial_bar_chart as radial_bar_chart
|
||||
from .charts import scatter_chart as scatter_chart
|
||||
from .charts import treemap as treemap
|
||||
from .general import Cell as Cell
|
||||
from .general import GraphingTooltip as GraphingTooltip
|
||||
from .general import Label as Label
|
||||
from .general import LabelList as LabelList
|
||||
from .general import Legend as Legend
|
||||
from .general import ResponsiveContainer as ResponsiveContainer
|
||||
from .general import cell as cell
|
||||
from .general import graphing_tooltip as graphing_tooltip
|
||||
from .general import label as label
|
||||
from .general import label_list as label_list
|
||||
|
@ -88,11 +88,13 @@ class ChartBase(RechartsCharts):
|
||||
"width": width if width is not None else "100%",
|
||||
"height": height if height is not None else "100%",
|
||||
}
|
||||
# Provide min dimensions so the graph always appears, even if the outer container is zero-size.
|
||||
if width is None:
|
||||
dim_props["min_width"] = 200
|
||||
if height is None:
|
||||
dim_props["min_height"] = 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)
|
||||
|
||||
return ResponsiveContainer.create(
|
||||
super().create(*children, **props),
|
||||
|
@ -36,11 +36,11 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
|
||||
# The height of chart container. Can be a number or string. Default: "100%"
|
||||
height: Var[Union[int, str]]
|
||||
|
||||
# The minimum width of chart container. Number
|
||||
min_width: Var[int]
|
||||
# The minimum width of chart container. Number or string.
|
||||
min_width: Var[Union[int, str]]
|
||||
|
||||
# The minimum height of chart container. Number
|
||||
min_height: Var[int]
|
||||
# The minimum height of chart container. Number or string.
|
||||
min_height: Var[Union[int, str]]
|
||||
|
||||
# If specified a positive number, debounced function will be used to handle the resize event. Default: 0
|
||||
debounce: Var[int]
|
||||
@ -242,8 +242,23 @@ class LabelList(Recharts):
|
||||
stroke: Var[Union[str, Color]] = LiteralVar.create("none")
|
||||
|
||||
|
||||
class Cell(Recharts):
|
||||
"""A Cell component in Recharts."""
|
||||
|
||||
tag = "Cell"
|
||||
|
||||
alias = "RechartsCell"
|
||||
|
||||
# The presentation attribute of a rectangle in bar or a sector in pie.
|
||||
fill: Var[str]
|
||||
|
||||
# The presentation attribute of a rectangle in bar or a sector in pie.
|
||||
stroke: Var[str]
|
||||
|
||||
|
||||
responsive_container = ResponsiveContainer.create
|
||||
legend = Legend.create
|
||||
graphing_tooltip = GraphingTooltip.create
|
||||
label = Label.create
|
||||
label_list = LabelList.create
|
||||
cell = Cell.create
|
||||
|
@ -22,8 +22,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
|
||||
aspect: Optional[Union[Var[int], int]] = None,
|
||||
width: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
||||
height: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
||||
min_width: Optional[Union[Var[int], int]] = None,
|
||||
min_height: Optional[Union[Var[int], int]] = None,
|
||||
min_width: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
||||
min_height: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
||||
debounce: Optional[Union[Var[int], int]] = None,
|
||||
style: Optional[Style] = None,
|
||||
key: Optional[Any] = None,
|
||||
@ -56,8 +56,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
|
||||
aspect: The aspect ratio of the container. The final aspect ratio of the SVG element will be (width / height) * aspect. Number
|
||||
width: The width of chart container. Can be a number or string. Default: "100%"
|
||||
height: The height of chart container. Can be a number or string. Default: "100%"
|
||||
min_width: The minimum width of chart container. Number
|
||||
min_height: The minimum height of chart container. Number
|
||||
min_width: The minimum width of chart container. Number or string.
|
||||
min_height: The minimum height of chart container. Number or string.
|
||||
debounce: If specified a positive number, debounced function will be used to handle the resize event. Default: 0
|
||||
on_resize: If specified provides a callback providing the updated chart width and height values.
|
||||
style: The style of the component.
|
||||
@ -482,8 +482,59 @@ class LabelList(Recharts):
|
||||
"""
|
||||
...
|
||||
|
||||
class Cell(Recharts):
|
||||
@overload
|
||||
@classmethod
|
||||
def create( # type: ignore
|
||||
cls,
|
||||
*children,
|
||||
fill: Optional[Union[Var[str], str]] = None,
|
||||
stroke: Optional[Union[Var[str], 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,
|
||||
) -> "Cell":
|
||||
"""Create the component.
|
||||
|
||||
Args:
|
||||
*children: The children of the component.
|
||||
fill: The presentation attribute of a rectangle in bar or a sector in pie.
|
||||
stroke: The presentation attribute of a rectangle in bar or a sector in pie.
|
||||
style: The style of the component.
|
||||
key: A unique key for the component.
|
||||
id: The id for the component.
|
||||
class_name: The class name for the component.
|
||||
autofocus: Whether the component should take the focus once the page is loaded
|
||||
custom_attrs: custom attribute
|
||||
**props: The props of the component.
|
||||
|
||||
Returns:
|
||||
The component.
|
||||
"""
|
||||
...
|
||||
|
||||
responsive_container = ResponsiveContainer.create
|
||||
legend = Legend.create
|
||||
graphing_tooltip = GraphingTooltip.create
|
||||
label = Label.create
|
||||
label_list = LabelList.create
|
||||
cell = Cell.create
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""The constants package."""
|
||||
|
||||
from .base import (
|
||||
APP_HARNESS_FLAG,
|
||||
COOKIES,
|
||||
IS_LINUX,
|
||||
IS_MACOS,
|
||||
|
@ -257,6 +257,7 @@ SESSION_STORAGE = "session_storage"
|
||||
# Testing variables.
|
||||
# Testing os env set by pytest when running a test case.
|
||||
PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
|
||||
APP_HARNESS_FLAG = "APP_HARNESS_FLAG"
|
||||
|
||||
REFLEX_VAR_OPENING_TAG = "<reflex.Var>"
|
||||
REFLEX_VAR_CLOSING_TAG = "</reflex.Var>"
|
||||
|
165
reflex/event.py
165
reflex/event.py
@ -25,7 +25,6 @@ from typing import (
|
||||
overload,
|
||||
)
|
||||
|
||||
import typing_extensions
|
||||
from typing_extensions import (
|
||||
Concatenate,
|
||||
ParamSpec,
|
||||
@ -43,6 +42,7 @@ from reflex.utils import console, format
|
||||
from reflex.utils.exceptions import (
|
||||
EventFnArgMismatchError,
|
||||
EventHandlerArgTypeMismatchError,
|
||||
MissingAnnotationError,
|
||||
)
|
||||
from reflex.utils.types import ArgsSpec, GenericType, typehint_issubclass
|
||||
from reflex.vars import VarData
|
||||
@ -99,32 +99,6 @@ _EVENT_FIELDS: set[str] = {f.name for f in dataclasses.fields(Event)}
|
||||
BACKGROUND_TASK_MARKER = "_reflex_background_task"
|
||||
|
||||
|
||||
def background(fn, *, __internal_reflex_call: bool = False):
|
||||
"""Decorator to mark event handler as running in the background.
|
||||
|
||||
Args:
|
||||
fn: The function to decorate.
|
||||
|
||||
Returns:
|
||||
The same function, but with a marker set.
|
||||
|
||||
|
||||
Raises:
|
||||
TypeError: If the function is not a coroutine function or async generator.
|
||||
"""
|
||||
if not __internal_reflex_call:
|
||||
console.deprecate(
|
||||
"background-decorator",
|
||||
"Use `rx.event(background=True)` instead.",
|
||||
"0.6.5",
|
||||
"0.7.0",
|
||||
)
|
||||
if not inspect.iscoroutinefunction(fn) and not inspect.isasyncgenfunction(fn):
|
||||
raise TypeError("Background task must be async function or generator.")
|
||||
setattr(fn, BACKGROUND_TASK_MARKER, True)
|
||||
return fn
|
||||
|
||||
|
||||
@dataclasses.dataclass(
|
||||
init=True,
|
||||
frozen=True,
|
||||
@ -816,29 +790,10 @@ def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec:
|
||||
)
|
||||
|
||||
|
||||
@overload
|
||||
def redirect(
|
||||
path: str | Var[str],
|
||||
is_external: Optional[bool] = None,
|
||||
is_external: bool = False,
|
||||
replace: bool = False,
|
||||
) -> EventSpec: ...
|
||||
|
||||
|
||||
@overload
|
||||
@typing_extensions.deprecated("`external` is deprecated use `is_external` instead")
|
||||
def redirect(
|
||||
path: str | Var[str],
|
||||
is_external: Optional[bool] = None,
|
||||
replace: bool = False,
|
||||
external: Optional[bool] = None,
|
||||
) -> EventSpec: ...
|
||||
|
||||
|
||||
def redirect(
|
||||
path: str | Var[str],
|
||||
is_external: Optional[bool] = None,
|
||||
replace: bool = False,
|
||||
external: Optional[bool] = None,
|
||||
) -> EventSpec:
|
||||
"""Redirect to a new path.
|
||||
|
||||
@ -846,26 +801,10 @@ def redirect(
|
||||
path: The path to redirect to.
|
||||
is_external: Whether to open in new tab or not.
|
||||
replace: If True, the current page will not create a new history entry.
|
||||
external(Deprecated): Whether to open in new tab or not.
|
||||
|
||||
Returns:
|
||||
An event to redirect to the path.
|
||||
"""
|
||||
if external is not None:
|
||||
console.deprecate(
|
||||
"The `external` prop in `rx.redirect`",
|
||||
"use `is_external` instead.",
|
||||
"0.6.6",
|
||||
"0.7.0",
|
||||
)
|
||||
|
||||
# is_external should take precedence over external.
|
||||
is_external = (
|
||||
(False if external is None else external)
|
||||
if is_external is None
|
||||
else is_external
|
||||
)
|
||||
|
||||
return server_side(
|
||||
"_redirect",
|
||||
get_fn_signature(redirect),
|
||||
@ -1282,11 +1221,14 @@ def call_event_handler(
|
||||
event_spec: The lambda that define the argument(s) to pass to the event handler.
|
||||
key: The key to pass to the event handler.
|
||||
|
||||
Raises:
|
||||
EventHandlerArgTypeMismatchError: If the event handler arguments do not match the event spec. #noqa: DAR402
|
||||
TypeError: If the event handler arguments are invalid.
|
||||
|
||||
Returns:
|
||||
The event spec from calling the event handler.
|
||||
|
||||
# noqa: DAR401 failure
|
||||
|
||||
#noqa: DAR401
|
||||
"""
|
||||
event_spec_args = parse_args_spec(event_spec) # type: ignore
|
||||
|
||||
@ -1323,10 +1265,15 @@ def call_event_handler(
|
||||
),
|
||||
)
|
||||
)
|
||||
type_match_found: dict[str, bool] = {}
|
||||
delayed_exceptions: list[EventHandlerArgTypeMismatchError] = []
|
||||
|
||||
try:
|
||||
type_hints_of_provided_callback = get_type_hints(event_callback.fn)
|
||||
except NameError:
|
||||
type_hints_of_provided_callback = {}
|
||||
|
||||
if event_spec_return_types:
|
||||
failures = []
|
||||
|
||||
event_callback_spec = inspect.getfullargspec(event_callback.fn)
|
||||
|
||||
for event_spec_index, event_spec_return_type in enumerate(
|
||||
@ -1338,43 +1285,35 @@ def call_event_handler(
|
||||
arg if get_origin(arg) is not Var else get_args(arg)[0] for arg in args
|
||||
]
|
||||
|
||||
try:
|
||||
type_hints_of_provided_callback = get_type_hints(event_callback.fn)
|
||||
except NameError:
|
||||
type_hints_of_provided_callback = {}
|
||||
|
||||
failed_type_check = False
|
||||
|
||||
# check that args of event handler are matching the spec if type hints are provided
|
||||
for i, arg in enumerate(event_callback_spec.args[1:]):
|
||||
if arg not in type_hints_of_provided_callback:
|
||||
continue
|
||||
|
||||
type_match_found.setdefault(arg, False)
|
||||
|
||||
try:
|
||||
compare_result = typehint_issubclass(
|
||||
args_types_without_vars[i], type_hints_of_provided_callback[arg]
|
||||
)
|
||||
except TypeError:
|
||||
# TODO: In 0.7.0, remove this block and raise the exception
|
||||
# raise TypeError(
|
||||
# f"Could not compare types {args_types_without_vars[i]} and {type_hints_of_provided_callback[arg]} for argument {arg} of {event_handler.fn.__qualname__} provided for {key}." # noqa: ERA001
|
||||
# ) from e
|
||||
console.warn(
|
||||
except TypeError as te:
|
||||
raise TypeError(
|
||||
f"Could not compare types {args_types_without_vars[i]} and {type_hints_of_provided_callback[arg]} for argument {arg} of {event_callback.fn.__qualname__} provided for {key}."
|
||||
)
|
||||
compare_result = False
|
||||
) from te
|
||||
|
||||
if compare_result:
|
||||
type_match_found[arg] = True
|
||||
continue
|
||||
else:
|
||||
failure = EventHandlerArgTypeMismatchError(
|
||||
f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {type_hints_of_provided_callback[arg]} as annotated in {event_callback.fn.__qualname__} instead."
|
||||
type_match_found[arg] = False
|
||||
delayed_exceptions.append(
|
||||
EventHandlerArgTypeMismatchError(
|
||||
f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {type_hints_of_provided_callback[arg]} as annotated in {event_callback.fn.__qualname__} instead."
|
||||
)
|
||||
)
|
||||
failures.append(failure)
|
||||
failed_type_check = True
|
||||
break
|
||||
|
||||
if not failed_type_check:
|
||||
if all(type_match_found.values()):
|
||||
delayed_exceptions.clear()
|
||||
if event_spec_index:
|
||||
args = get_args(event_spec_return_types[0])
|
||||
|
||||
@ -1396,15 +1335,10 @@ def call_event_handler(
|
||||
f"Event handler {key} expects ({expect_string}) -> () but got ({given_string}) -> () as annotated in {event_callback.fn.__qualname__} instead. "
|
||||
f"This may lead to unexpected behavior but is intentionally ignored for {key}."
|
||||
)
|
||||
return event_callback(*event_spec_args)
|
||||
break
|
||||
|
||||
if failures:
|
||||
console.deprecate(
|
||||
"Mismatched event handler argument types",
|
||||
"\n".join([str(f) for f in failures]),
|
||||
"0.6.5",
|
||||
"0.7.0",
|
||||
)
|
||||
if delayed_exceptions:
|
||||
raise delayed_exceptions[0]
|
||||
|
||||
return event_callback(*event_spec_args) # type: ignore
|
||||
|
||||
@ -1423,26 +1357,26 @@ def unwrap_var_annotation(annotation: GenericType):
|
||||
return annotation
|
||||
|
||||
|
||||
def resolve_annotation(annotations: dict[str, Any], arg_name: str):
|
||||
def resolve_annotation(annotations: dict[str, Any], arg_name: str, spec: ArgsSpec):
|
||||
"""Resolve the annotation for the given argument name.
|
||||
|
||||
Args:
|
||||
annotations: The annotations.
|
||||
arg_name: The argument name.
|
||||
spec: The specs which the annotations come from.
|
||||
|
||||
Raises:
|
||||
MissingAnnotationError: If the annotation is missing for non-lambda methods.
|
||||
|
||||
Returns:
|
||||
The resolved annotation.
|
||||
"""
|
||||
annotation = annotations.get(arg_name)
|
||||
if annotation is None:
|
||||
console.deprecate(
|
||||
feature_name="Unannotated event handler arguments",
|
||||
reason="Provide type annotations for event handler arguments.",
|
||||
deprecation_version="0.6.3",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
# Allow arbitrary attribute access two levels deep until removed.
|
||||
return Dict[str, dict]
|
||||
if not isinstance(spec, types.LambdaType):
|
||||
raise MissingAnnotationError(var_name=arg_name)
|
||||
else:
|
||||
return dict[str, dict]
|
||||
return annotation
|
||||
|
||||
|
||||
@ -1464,7 +1398,13 @@ def parse_args_spec(arg_spec: ArgsSpec | Sequence[ArgsSpec]):
|
||||
arg_spec(
|
||||
*[
|
||||
Var(f"_{l_arg}").to(
|
||||
unwrap_var_annotation(resolve_annotation(annotations, l_arg))
|
||||
unwrap_var_annotation(
|
||||
resolve_annotation(
|
||||
annotations,
|
||||
l_arg,
|
||||
spec=arg_spec,
|
||||
)
|
||||
)
|
||||
)
|
||||
for l_arg in spec.args
|
||||
]
|
||||
@ -1815,8 +1755,6 @@ V3 = TypeVar("V3")
|
||||
V4 = TypeVar("V4")
|
||||
V5 = TypeVar("V5")
|
||||
|
||||
background_event_decorator = background
|
||||
|
||||
|
||||
class EventCallback(Generic[P, T]):
|
||||
"""A descriptor that wraps a function to be used as an event."""
|
||||
@ -1989,6 +1927,9 @@ class EventNamespace(types.SimpleNamespace):
|
||||
func: The function to wrap.
|
||||
background: Whether the event should be run in the background. Defaults to False.
|
||||
|
||||
Raises:
|
||||
TypeError: If background is True and the function is not a coroutine or async generator. # noqa: DAR402
|
||||
|
||||
Returns:
|
||||
The wrapped function.
|
||||
"""
|
||||
@ -1997,7 +1938,13 @@ class EventNamespace(types.SimpleNamespace):
|
||||
func: Callable[Concatenate[BASE_STATE, P], T],
|
||||
) -> EventCallback[P, T]:
|
||||
if background is True:
|
||||
return background_event_decorator(func, __internal_reflex_call=True) # type: ignore
|
||||
if not inspect.iscoroutinefunction(
|
||||
func
|
||||
) and not inspect.isasyncgenfunction(func):
|
||||
raise TypeError(
|
||||
"Background task must be async function or generator."
|
||||
)
|
||||
setattr(func, BACKGROUND_TASK_MARKER, True)
|
||||
return func # type: ignore
|
||||
|
||||
if func is not None:
|
||||
|
@ -9,7 +9,6 @@ from reflex.components.sonner.toast import toast as toast
|
||||
|
||||
from ..utils.console import warn
|
||||
from . import hooks as hooks
|
||||
from .assets import asset as asset
|
||||
from .client_state import ClientStateVar as ClientStateVar
|
||||
from .layout import layout as layout
|
||||
from .misc import run_in_thread as run_in_thread
|
||||
@ -62,7 +61,6 @@ class ExperimentalNamespace(SimpleNamespace):
|
||||
|
||||
|
||||
_x = ExperimentalNamespace(
|
||||
asset=asset,
|
||||
client_state=ClientStateVar.create,
|
||||
hooks=hooks,
|
||||
layout=layout,
|
||||
|
@ -1,37 +0,0 @@
|
||||
"""Helper functions for adding assets to the app."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from reflex import assets
|
||||
from reflex.utils import console
|
||||
|
||||
|
||||
def asset(relative_filename: str, subfolder: Optional[str] = None) -> str:
|
||||
"""DEPRECATED: use `rx.asset` with `shared=True` instead.
|
||||
|
||||
Add an asset to the app.
|
||||
Place the file next to your including python file.
|
||||
Copies the file to the app's external assets directory.
|
||||
|
||||
Example:
|
||||
```python
|
||||
rx.script(src=rx._x.asset("my_custom_javascript.js"))
|
||||
rx.image(src=rx._x.asset("test_image.png","subfolder"))
|
||||
```
|
||||
|
||||
Args:
|
||||
relative_filename: The relative filename of the asset.
|
||||
subfolder: The directory to place the asset in.
|
||||
|
||||
Returns:
|
||||
The relative URL to the copied asset.
|
||||
"""
|
||||
console.deprecate(
|
||||
feature_name="rx._x.asset",
|
||||
reason="Use `rx.asset` with `shared=True` instead of `rx._x.asset`.",
|
||||
deprecation_version="0.6.6",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
return assets.asset(
|
||||
relative_filename, shared=True, subfolder=subfolder, _stack_level=2
|
||||
)
|
@ -306,6 +306,9 @@ def export(
|
||||
help="Whether to exclude sqlite db files when exporting backend.",
|
||||
hidden=True,
|
||||
),
|
||||
env: constants.Env = typer.Option(
|
||||
constants.Env.PROD, help="The environment to export the app in."
|
||||
),
|
||||
loglevel: constants.LogLevel = typer.Option(
|
||||
config.loglevel, help="The log level to use."
|
||||
),
|
||||
@ -323,6 +326,7 @@ def export(
|
||||
backend=backend,
|
||||
zip_dest_dir=zip_dest_dir,
|
||||
upload_db_file=upload_db_file,
|
||||
env=env,
|
||||
loglevel=loglevel.subprocess_level(),
|
||||
)
|
||||
|
||||
@ -440,7 +444,11 @@ def deploy(
|
||||
config.app_name,
|
||||
"--app-name",
|
||||
help="The name of the App to deploy under.",
|
||||
hidden=True,
|
||||
),
|
||||
app_id: str = typer.Option(
|
||||
None,
|
||||
"--app-id",
|
||||
help="The ID of the App to deploy over.",
|
||||
),
|
||||
regions: List[str] = typer.Option(
|
||||
[],
|
||||
@ -480,6 +488,11 @@ def deploy(
|
||||
"--project",
|
||||
help="project id to deploy to",
|
||||
),
|
||||
project_name: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--project-name",
|
||||
help="The name of the project to deploy to.",
|
||||
),
|
||||
token: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--token",
|
||||
@ -492,6 +505,7 @@ def deploy(
|
||||
),
|
||||
):
|
||||
"""Deploy the app to the Reflex hosting service."""
|
||||
from reflex_cli.constants.base import LogLevel as HostingLogLevel
|
||||
from reflex_cli.utils import dependency
|
||||
from reflex_cli.v2 import cli as hosting_cli
|
||||
|
||||
@ -503,12 +517,20 @@ def deploy(
|
||||
# Set the log level.
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
if not token:
|
||||
# make sure user is logged in.
|
||||
if interactive:
|
||||
hosting_cli.login()
|
||||
else:
|
||||
raise SystemExit("Token is required for non-interactive mode.")
|
||||
def convert_reflex_loglevel_to_reflex_cli_loglevel(
|
||||
loglevel: constants.LogLevel,
|
||||
) -> HostingLogLevel:
|
||||
if loglevel == constants.LogLevel.DEBUG:
|
||||
return HostingLogLevel.DEBUG
|
||||
if loglevel == constants.LogLevel.INFO:
|
||||
return HostingLogLevel.INFO
|
||||
if loglevel == constants.LogLevel.WARNING:
|
||||
return HostingLogLevel.WARNING
|
||||
if loglevel == constants.LogLevel.ERROR:
|
||||
return HostingLogLevel.ERROR
|
||||
if loglevel == constants.LogLevel.CRITICAL:
|
||||
return HostingLogLevel.CRITICAL
|
||||
return HostingLogLevel.INFO
|
||||
|
||||
# Only check requirements if interactive.
|
||||
# There is user interaction for requirements update.
|
||||
@ -519,11 +541,10 @@ def deploy(
|
||||
if prerequisites.needs_reinit(frontend=True):
|
||||
_init(name=config.app_name, loglevel=loglevel)
|
||||
prerequisites.check_latest_package_version(constants.ReflexHostingCLI.MODULE_NAME)
|
||||
extra: dict[str, str] = (
|
||||
{"config_path": config_path} if config_path is not None else {}
|
||||
)
|
||||
|
||||
hosting_cli.deploy(
|
||||
app_name=app_name,
|
||||
app_id=app_id,
|
||||
export_fn=lambda zip_dest_dir,
|
||||
api_url,
|
||||
deploy_url,
|
||||
@ -544,10 +565,11 @@ def deploy(
|
||||
envfile=envfile,
|
||||
hostname=hostname,
|
||||
interactive=interactive,
|
||||
loglevel=type(loglevel).INFO, # type: ignore
|
||||
loglevel=convert_reflex_loglevel_to_reflex_cli_loglevel(loglevel),
|
||||
token=token,
|
||||
project=project,
|
||||
**extra,
|
||||
project_name=project_name,
|
||||
**({"config_path": config_path} if config_path is not None else {}),
|
||||
)
|
||||
|
||||
|
||||
|
@ -1341,12 +1341,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
||||
if field.allow_none and not is_optional(field_type):
|
||||
field_type = Union[field_type, None]
|
||||
if not _isinstance(value, field_type):
|
||||
console.deprecate(
|
||||
"mismatched-type-assignment",
|
||||
f"Tried to assign value {value} of type {type(value)} to field {type(self).__name__}.{name} of type {field_type}."
|
||||
" This might lead to unexpected behavior.",
|
||||
"0.6.5",
|
||||
"0.7.0",
|
||||
console.error(
|
||||
f"Expected field '{type(self).__name__}.{name}' to receive type '{field_type}',"
|
||||
f" but got '{value}' of type '{type(value)}'."
|
||||
)
|
||||
|
||||
# Set the attribute.
|
||||
@ -1831,7 +1828,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
||||
if (
|
||||
isinstance(value, dict)
|
||||
and inspect.isclass(hinted_args)
|
||||
and not types.is_generic_alias(hinted_args) # py3.9-py3.10
|
||||
and not types.is_generic_alias(hinted_args) # py3.10
|
||||
):
|
||||
if issubclass(hinted_args, Model):
|
||||
# Remove non-fields from the payload
|
||||
|
@ -287,7 +287,9 @@ class Style(dict):
|
||||
_var = LiteralVar.create(value)
|
||||
if _var is not None:
|
||||
# Carry the imports/hooks when setting a Var as a value.
|
||||
self._var_data = VarData.merge(self._var_data, _var._get_all_var_data())
|
||||
self._var_data = VarData.merge(
|
||||
getattr(self, "_var_data", None), _var._get_all_var_data()
|
||||
)
|
||||
super().__setitem__(key, value)
|
||||
|
||||
|
||||
|
@ -282,6 +282,7 @@ class AppHarness:
|
||||
before_decorated_pages = reflex.app.DECORATED_PAGES[self.app_name].copy()
|
||||
# Ensure the AppHarness test does not skip State assignment due to running via pytest
|
||||
os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None)
|
||||
os.environ[reflex.constants.APP_HARNESS_FLAG] = "true"
|
||||
self.app_module = reflex.utils.prerequisites.get_compiled_app(
|
||||
# Do not reload the module for pre-existing apps (only apps generated from source)
|
||||
reload=self.app_source is not None
|
||||
@ -932,6 +933,7 @@ class AppHarnessProd(AppHarness):
|
||||
frontend=True,
|
||||
backend=False,
|
||||
loglevel=reflex.constants.LogLevel.INFO,
|
||||
env=reflex.constants.Env.PROD,
|
||||
)
|
||||
|
||||
self.frontend_thread = threading.Thread(target=self._run_frontend)
|
||||
|
@ -13,13 +13,17 @@ from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
||||
from reflex import constants
|
||||
from reflex.config import get_config
|
||||
from reflex.utils import console, path_ops, prerequisites, processes
|
||||
from reflex.utils.exec import is_in_app_harness
|
||||
|
||||
|
||||
def set_env_json():
|
||||
"""Write the upload url to a REFLEX_JSON."""
|
||||
path_ops.update_json_file(
|
||||
str(prerequisites.get_web_dir() / constants.Dirs.ENV_JSON),
|
||||
{endpoint.name: endpoint.get_url() for endpoint in constants.Endpoint},
|
||||
{
|
||||
**{endpoint.name: endpoint.get_url() for endpoint in constants.Endpoint},
|
||||
"TEST_MODE": is_in_app_harness(),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
@ -51,20 +51,12 @@ def set_log_level(log_level: LogLevel):
|
||||
log_level: The log level to set.
|
||||
|
||||
Raises:
|
||||
ValueError: If the log level is invalid.
|
||||
TypeError: If the log level is a string.
|
||||
"""
|
||||
if not isinstance(log_level, LogLevel):
|
||||
deprecate(
|
||||
feature_name="Passing a string to set_log_level",
|
||||
reason="use reflex.constants.LogLevel enum instead",
|
||||
deprecation_version="0.6.6",
|
||||
removal_version="0.7.0",
|
||||
raise TypeError(
|
||||
f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead."
|
||||
)
|
||||
try:
|
||||
log_level = getattr(LogLevel, log_level.upper())
|
||||
except AttributeError as ae:
|
||||
raise ValueError(f"Invalid log level: {log_level}") from ae
|
||||
|
||||
global _LOG_LEVEL
|
||||
_LOG_LEVEL = log_level
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Custom Exceptions."""
|
||||
|
||||
from typing import Any, NoReturn
|
||||
from typing import Any
|
||||
|
||||
|
||||
class ReflexError(Exception):
|
||||
@ -75,6 +75,30 @@ class VarAttributeError(ReflexError, AttributeError):
|
||||
"""Custom AttributeError for var related errors."""
|
||||
|
||||
|
||||
class UntypedComputedVarError(ReflexError, TypeError):
|
||||
"""Custom TypeError for untyped computed var errors."""
|
||||
|
||||
def __init__(self, var_name):
|
||||
"""Initialize the UntypedComputedVarError.
|
||||
|
||||
Args:
|
||||
var_name: The name of the computed var.
|
||||
"""
|
||||
super().__init__(f"Computed var '{var_name}' must have a type annotation.")
|
||||
|
||||
|
||||
class MissingAnnotationError(ReflexError, TypeError):
|
||||
"""Custom TypeError for missing annotations."""
|
||||
|
||||
def __init__(self, var_name):
|
||||
"""Initialize the MissingAnnotationError.
|
||||
|
||||
Args:
|
||||
var_name: The name of the var.
|
||||
"""
|
||||
super().__init__(f"Var '{var_name}' must have a type annotation.")
|
||||
|
||||
|
||||
class UploadValueError(ReflexError, ValueError):
|
||||
"""Custom ValueError for upload related errors."""
|
||||
|
||||
@ -186,29 +210,27 @@ class StateMismatchError(ReflexError, ValueError):
|
||||
class SystemPackageMissingError(ReflexError):
|
||||
"""Raised when a system package is missing."""
|
||||
|
||||
def __init__(self, package: str):
|
||||
"""Initialize the SystemPackageMissingError.
|
||||
|
||||
Args:
|
||||
package: The missing package.
|
||||
"""
|
||||
from reflex.constants import IS_MACOS
|
||||
|
||||
extra = (
|
||||
f" You can do so by running 'brew install {package}'." if IS_MACOS else ""
|
||||
)
|
||||
super().__init__(
|
||||
f"System package '{package}' is missing."
|
||||
f" Please install it through your system package manager.{extra}"
|
||||
)
|
||||
|
||||
|
||||
class EventDeserializationError(ReflexError, ValueError):
|
||||
"""Raised when an event cannot be deserialized."""
|
||||
|
||||
|
||||
def raise_system_package_missing_error(package: str) -> NoReturn:
|
||||
"""Raise a SystemPackageMissingError.
|
||||
|
||||
Args:
|
||||
package: The name of the missing system package.
|
||||
|
||||
Raises:
|
||||
SystemPackageMissingError: The raised exception.
|
||||
"""
|
||||
from reflex.constants import IS_MACOS
|
||||
|
||||
raise SystemPackageMissingError(
|
||||
f"System package '{package}' is missing."
|
||||
" Please install it through your system package manager."
|
||||
+ (f" You can do so by running 'brew install {package}'." if IS_MACOS else "")
|
||||
)
|
||||
|
||||
|
||||
class InvalidLockWarningThresholdError(ReflexError):
|
||||
"""Raised when an invalid lock warning threshold is provided."""
|
||||
|
||||
|
@ -240,25 +240,28 @@ def run_backend(
|
||||
run_uvicorn_backend(host, port, loglevel)
|
||||
|
||||
|
||||
def get_reload_dirs() -> list[str]:
|
||||
def get_reload_dirs() -> list[Path]:
|
||||
"""Get the reload directories for the backend.
|
||||
|
||||
Returns:
|
||||
The reload directories for the backend.
|
||||
"""
|
||||
config = get_config()
|
||||
reload_dirs = [config.app_name]
|
||||
reload_dirs = [Path(config.app_name)]
|
||||
if config.app_module is not None and config.app_module.__file__:
|
||||
module_path = Path(config.app_module.__file__).resolve().parent
|
||||
|
||||
while module_path.parent.name:
|
||||
for parent_file in module_path.parent.iterdir():
|
||||
if parent_file == "__init__.py":
|
||||
# go up a level to find dir without `__init__.py`
|
||||
module_path = module_path.parent
|
||||
break
|
||||
if any(
|
||||
sibling_file.name == "__init__.py"
|
||||
for sibling_file in module_path.parent.iterdir()
|
||||
):
|
||||
# go up a level to find dir without `__init__.py`
|
||||
module_path = module_path.parent
|
||||
else:
|
||||
break
|
||||
reload_dirs.append(str(module_path))
|
||||
|
||||
reload_dirs = [module_path]
|
||||
return reload_dirs
|
||||
|
||||
|
||||
@ -278,7 +281,7 @@ def run_uvicorn_backend(host, port, loglevel: LogLevel):
|
||||
port=port,
|
||||
log_level=loglevel.value,
|
||||
reload=True,
|
||||
reload_dirs=get_reload_dirs(),
|
||||
reload_dirs=list(map(str, get_reload_dirs())),
|
||||
)
|
||||
|
||||
|
||||
@ -509,6 +512,15 @@ def is_testing_env() -> bool:
|
||||
return constants.PYTEST_CURRENT_TEST in os.environ
|
||||
|
||||
|
||||
def is_in_app_harness() -> bool:
|
||||
"""Whether the app is running in the app harness.
|
||||
|
||||
Returns:
|
||||
True if the app is running in the app harness.
|
||||
"""
|
||||
return constants.APP_HARNESS_FLAG in os.environ
|
||||
|
||||
|
||||
def is_prod_mode() -> bool:
|
||||
"""Check if the app is running in production mode.
|
||||
|
||||
@ -517,48 +529,3 @@ def is_prod_mode() -> bool:
|
||||
"""
|
||||
current_mode = environment.REFLEX_ENV_MODE.get()
|
||||
return current_mode == constants.Env.PROD
|
||||
|
||||
|
||||
def is_frontend_only() -> bool:
|
||||
"""Check if the app is running in frontend-only mode.
|
||||
|
||||
Returns:
|
||||
True if the app is running in frontend-only mode.
|
||||
"""
|
||||
console.deprecate(
|
||||
"is_frontend_only() is deprecated and will be removed in a future release.",
|
||||
reason="Use `environment.REFLEX_FRONTEND_ONLY.get()` instead.",
|
||||
deprecation_version="0.6.5",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
return environment.REFLEX_FRONTEND_ONLY.get()
|
||||
|
||||
|
||||
def is_backend_only() -> bool:
|
||||
"""Check if the app is running in backend-only mode.
|
||||
|
||||
Returns:
|
||||
True if the app is running in backend-only mode.
|
||||
"""
|
||||
console.deprecate(
|
||||
"is_backend_only() is deprecated and will be removed in a future release.",
|
||||
reason="Use `environment.REFLEX_BACKEND_ONLY.get()` instead.",
|
||||
deprecation_version="0.6.5",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
return environment.REFLEX_BACKEND_ONLY.get()
|
||||
|
||||
|
||||
def should_skip_compile() -> bool:
|
||||
"""Whether the app should skip compile.
|
||||
|
||||
Returns:
|
||||
True if the app should skip compile.
|
||||
"""
|
||||
console.deprecate(
|
||||
"should_skip_compile() is deprecated and will be removed in a future release.",
|
||||
reason="Use `environment.REFLEX_SKIP_COMPILE.get()` instead.",
|
||||
deprecation_version="0.6.5",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
return environment.REFLEX_SKIP_COMPILE.get()
|
||||
|
@ -4,7 +4,7 @@ from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from reflex import constants
|
||||
from reflex.config import get_config
|
||||
from reflex.config import environment, get_config
|
||||
from reflex.utils import build, console, exec, prerequisites, telemetry
|
||||
|
||||
config = get_config()
|
||||
@ -18,6 +18,7 @@ def export(
|
||||
upload_db_file: bool = False,
|
||||
api_url: Optional[str] = None,
|
||||
deploy_url: Optional[str] = None,
|
||||
env: constants.Env = constants.Env.PROD,
|
||||
loglevel: constants.LogLevel = console._LOG_LEVEL,
|
||||
):
|
||||
"""Export the app to a zip file.
|
||||
@ -30,11 +31,15 @@ def export(
|
||||
upload_db_file: Whether to upload the database file. Defaults to False.
|
||||
api_url: The API URL to use. Defaults to None.
|
||||
deploy_url: The deploy URL to use. Defaults to None.
|
||||
env: The environment to use. Defaults to constants.Env.PROD.
|
||||
loglevel: The log level to use. Defaults to console._LOG_LEVEL.
|
||||
"""
|
||||
# Set the log level.
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
# Set env mode in the environment
|
||||
environment.REFLEX_ENV_MODE.set(env)
|
||||
|
||||
# Override the config url values if provided.
|
||||
if api_url is not None:
|
||||
config.api_url = str(api_url)
|
||||
|
@ -11,7 +11,6 @@ from typing import TYPE_CHECKING, Any, List, Optional, Union
|
||||
from reflex import constants
|
||||
from reflex.constants.state import FRONTEND_EVENT_STATE
|
||||
from reflex.utils import exceptions
|
||||
from reflex.utils.console import deprecate
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from reflex.components.component import ComponentStyle
|
||||
@ -502,37 +501,6 @@ if TYPE_CHECKING:
|
||||
from reflex.vars import Var
|
||||
|
||||
|
||||
def format_event_chain(
|
||||
event_chain: EventChain | Var[EventChain],
|
||||
event_arg: Var | None = None,
|
||||
) -> str:
|
||||
"""DEPRECATED: format an event chain as a javascript invocation.
|
||||
|
||||
Use str(rx.Var.create(event_chain)) instead.
|
||||
|
||||
Args:
|
||||
event_chain: The event chain to format.
|
||||
event_arg: this argument is ignored.
|
||||
|
||||
Returns:
|
||||
Compiled javascript code to queue the given event chain on the frontend.
|
||||
"""
|
||||
deprecate(
|
||||
feature_name="format_event_chain",
|
||||
reason="Use str(rx.Var.create(event_chain)) instead",
|
||||
deprecation_version="0.6.0",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
|
||||
from reflex.vars import Var
|
||||
from reflex.vars.function import ArgsFunctionOperation
|
||||
|
||||
result = Var.create(event_chain)
|
||||
if isinstance(result, ArgsFunctionOperation):
|
||||
result = result._return_expr
|
||||
return str(result)
|
||||
|
||||
|
||||
def format_queue_events(
|
||||
events: EventType | None = None,
|
||||
args_spec: Optional[ArgsSpec] = None,
|
||||
|
@ -174,7 +174,7 @@ def get_node_path() -> str | None:
|
||||
return str(node_path)
|
||||
|
||||
|
||||
def get_npm_path() -> str | None:
|
||||
def get_npm_path() -> Path | None:
|
||||
"""Get npm binary path.
|
||||
|
||||
Returns:
|
||||
@ -183,8 +183,8 @@ def get_npm_path() -> str | None:
|
||||
npm_path = Path(constants.Node.NPM_PATH)
|
||||
if use_system_node() or not npm_path.exists():
|
||||
system_npm_path = which("npm")
|
||||
return str(system_npm_path) if system_npm_path else None
|
||||
return str(npm_path)
|
||||
npm_path = Path(system_npm_path) if system_npm_path else None
|
||||
return npm_path.absolute() if npm_path else None
|
||||
|
||||
|
||||
def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]):
|
||||
|
@ -38,7 +38,7 @@ from reflex.config import Config, environment, get_config
|
||||
from reflex.utils import console, net, path_ops, processes, redir
|
||||
from reflex.utils.exceptions import (
|
||||
GeneratedCodeHasNoFunctionDefsError,
|
||||
raise_system_package_missing_error,
|
||||
SystemPackageMissingError,
|
||||
)
|
||||
from reflex.utils.format import format_library_name
|
||||
from reflex.utils.registry import _get_npm_registry
|
||||
@ -86,18 +86,6 @@ def get_web_dir() -> Path:
|
||||
return environment.REFLEX_WEB_WORKDIR.get()
|
||||
|
||||
|
||||
def _python_version_check():
|
||||
"""Emit deprecation warning for deprecated python versions."""
|
||||
# Check for end-of-life python versions.
|
||||
if sys.version_info < (3, 10):
|
||||
console.deprecate(
|
||||
feature_name="Support for Python 3.9 and older",
|
||||
reason="please upgrade to Python 3.10 or newer",
|
||||
deprecation_version="0.6.0",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
|
||||
|
||||
def check_latest_package_version(package_name: str):
|
||||
"""Check if the latest version of the package is installed.
|
||||
|
||||
@ -120,8 +108,6 @@ def check_latest_package_version(package_name: str):
|
||||
console.warn(
|
||||
f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
|
||||
)
|
||||
# Check for deprecated python versions
|
||||
_python_version_check()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -254,7 +240,7 @@ def get_package_manager(on_failure_return_none: bool = False) -> str | None:
|
||||
"""
|
||||
npm_path = path_ops.get_npm_path()
|
||||
if npm_path is not None:
|
||||
return str(Path(npm_path).resolve())
|
||||
return str(npm_path)
|
||||
if on_failure_return_none:
|
||||
return None
|
||||
raise FileNotFoundError("NPM not found. You may need to run `reflex init`.")
|
||||
@ -885,7 +871,11 @@ def install_node():
|
||||
|
||||
|
||||
def install_bun():
|
||||
"""Install bun onto the user's system."""
|
||||
"""Install bun onto the user's system.
|
||||
|
||||
Raises:
|
||||
SystemPackageMissingError: If "unzip" is missing.
|
||||
"""
|
||||
win_supported = is_windows_bun_supported()
|
||||
one_drive_in_path = windows_check_onedrive_in_path()
|
||||
if constants.IS_WINDOWS and (not win_supported or one_drive_in_path):
|
||||
@ -924,7 +914,7 @@ def install_bun():
|
||||
else:
|
||||
unzip_path = path_ops.which("unzip")
|
||||
if unzip_path is None:
|
||||
raise_system_package_missing_error("unzip")
|
||||
raise SystemPackageMissingError("unzip")
|
||||
|
||||
# Run the bun install script.
|
||||
download_and_run(
|
||||
|
@ -9,7 +9,6 @@ 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
|
||||
@ -368,7 +367,7 @@ def get_command_with_loglevel(command: list[str]) -> list[str]:
|
||||
The updated command list
|
||||
"""
|
||||
npm_path = path_ops.get_npm_path()
|
||||
npm_path = str(Path(npm_path).resolve()) if npm_path else npm_path
|
||||
npm_path = str(npm_path) if npm_path else None
|
||||
|
||||
if command[0] == npm_path:
|
||||
return [*command, "--loglevel", "silly"]
|
||||
|
@ -122,7 +122,7 @@ def _prepare_event(event: str, **kwargs) -> dict:
|
||||
return {}
|
||||
|
||||
if UTC is None:
|
||||
# for python 3.9 & 3.10
|
||||
# for python 3.10
|
||||
stamp = datetime.utcnow().isoformat()
|
||||
else:
|
||||
# for python 3.11 & 3.12
|
||||
|
@ -829,6 +829,22 @@ StateBases = get_base_class(StateVar)
|
||||
StateIterBases = get_base_class(StateIterVar)
|
||||
|
||||
|
||||
def safe_issubclass(cls: Type, cls_check: Type | Tuple[Type, ...]):
|
||||
"""Check if a class is a subclass of another class. Returns False if internal error occurs.
|
||||
|
||||
Args:
|
||||
cls: The class to check.
|
||||
cls_check: The class to check against.
|
||||
|
||||
Returns:
|
||||
Whether the class is a subclass of the other class.
|
||||
"""
|
||||
try:
|
||||
return issubclass(cls, cls_check)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
||||
def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> bool:
|
||||
"""Check if a type hint is a subclass of another type hint.
|
||||
|
||||
|
@ -26,6 +26,7 @@ from typing import (
|
||||
Iterable,
|
||||
List,
|
||||
Literal,
|
||||
Mapping,
|
||||
NoReturn,
|
||||
Optional,
|
||||
Set,
|
||||
@ -45,6 +46,7 @@ from reflex.base import Base
|
||||
from reflex.constants.compiler import Hooks
|
||||
from reflex.utils import console, exceptions, imports, serializers, types
|
||||
from reflex.utils.exceptions import (
|
||||
UntypedComputedVarError,
|
||||
VarAttributeError,
|
||||
VarDependencyError,
|
||||
VarTypeError,
|
||||
@ -64,6 +66,7 @@ from reflex.utils.types import (
|
||||
_isinstance,
|
||||
get_origin,
|
||||
has_args,
|
||||
safe_issubclass,
|
||||
unionize,
|
||||
)
|
||||
|
||||
@ -127,7 +130,7 @@ class VarData:
|
||||
state: str = "",
|
||||
field_name: str = "",
|
||||
imports: ImportDict | ParsedImportDict | None = None,
|
||||
hooks: dict[str, VarData | None] | None = None,
|
||||
hooks: Mapping[str, VarData | None] | None = None,
|
||||
deps: list[Var] | None = None,
|
||||
position: Hooks.HookPosition | None = None,
|
||||
):
|
||||
@ -543,52 +546,21 @@ class Var(Generic[VAR_TYPE]):
|
||||
def create(
|
||||
cls,
|
||||
value: Any,
|
||||
_var_is_local: bool | None = None,
|
||||
_var_is_string: bool | None = None,
|
||||
_var_data: VarData | None = None,
|
||||
) -> Var:
|
||||
"""Create a var from a value.
|
||||
|
||||
Args:
|
||||
value: The value to create the var from.
|
||||
_var_is_local: Whether the var is local. Deprecated.
|
||||
_var_is_string: Whether the var is a string literal. Deprecated.
|
||||
_var_data: Additional hooks and imports associated with the Var.
|
||||
|
||||
Returns:
|
||||
The var.
|
||||
"""
|
||||
if _var_is_local is not None:
|
||||
console.deprecate(
|
||||
feature_name="_var_is_local",
|
||||
reason="The _var_is_local argument is not supported for Var. "
|
||||
"If you want to create a Var from a raw Javascript expression, use the constructor directly",
|
||||
deprecation_version="0.6.0",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
if _var_is_string is not None:
|
||||
console.deprecate(
|
||||
feature_name="_var_is_string",
|
||||
reason="The _var_is_string argument is not supported for Var. "
|
||||
"If you want to create a Var from a raw Javascript expression, use the constructor directly",
|
||||
deprecation_version="0.6.0",
|
||||
removal_version="0.7.0",
|
||||
)
|
||||
|
||||
# If the value is already a var, do nothing.
|
||||
if isinstance(value, Var):
|
||||
return value
|
||||
|
||||
# Try to pull the imports and hooks from contained values.
|
||||
if not isinstance(value, str):
|
||||
return LiteralVar.create(value, _var_data=_var_data)
|
||||
|
||||
if _var_is_string is False or _var_is_local is True:
|
||||
return cls(
|
||||
_js_expr=value,
|
||||
_var_data=_var_data,
|
||||
)
|
||||
|
||||
return LiteralVar.create(value, _var_data=_var_data)
|
||||
|
||||
@classmethod
|
||||
@ -643,8 +615,8 @@ class Var(Generic[VAR_TYPE]):
|
||||
@overload
|
||||
def to(
|
||||
self,
|
||||
output: type[dict],
|
||||
) -> ObjectVar[dict]: ...
|
||||
output: type[Mapping],
|
||||
) -> ObjectVar[Mapping]: ...
|
||||
|
||||
@overload
|
||||
def to(
|
||||
@ -686,7 +658,9 @@ class Var(Generic[VAR_TYPE]):
|
||||
|
||||
# If the first argument is a python type, we map it to the corresponding Var type.
|
||||
for var_subclass in _var_subclasses[::-1]:
|
||||
if fixed_output_type in var_subclass.python_types:
|
||||
if fixed_output_type in var_subclass.python_types or safe_issubclass(
|
||||
fixed_output_type, var_subclass.python_types
|
||||
):
|
||||
return self.to(var_subclass.var_subclass, output)
|
||||
|
||||
if fixed_output_type is None:
|
||||
@ -820,7 +794,7 @@ class Var(Generic[VAR_TYPE]):
|
||||
return False
|
||||
if issubclass(type_, list):
|
||||
return []
|
||||
if issubclass(type_, dict):
|
||||
if issubclass(type_, Mapping):
|
||||
return {}
|
||||
if issubclass(type_, tuple):
|
||||
return ()
|
||||
@ -1026,7 +1000,7 @@ class Var(Generic[VAR_TYPE]):
|
||||
f"$/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")]
|
||||
}
|
||||
),
|
||||
).to(ObjectVar, Dict[str, str])
|
||||
).to(ObjectVar, Mapping[str, str])
|
||||
return refs[LiteralVar.create(str(self))]
|
||||
|
||||
@deprecated("Use `.js_type()` instead.")
|
||||
@ -1373,7 +1347,7 @@ class LiteralVar(Var):
|
||||
|
||||
serialized_value = serializers.serialize(value)
|
||||
if serialized_value is not None:
|
||||
if isinstance(serialized_value, dict):
|
||||
if isinstance(serialized_value, Mapping):
|
||||
return LiteralObjectVar.create(
|
||||
serialized_value,
|
||||
_var_type=type(value),
|
||||
@ -1498,7 +1472,7 @@ def var_operation(
|
||||
) -> Callable[P, ArrayVar[LIST_T]]: ...
|
||||
|
||||
|
||||
OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Dict)
|
||||
OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Mapping)
|
||||
|
||||
|
||||
@overload
|
||||
@ -1573,8 +1547,8 @@ def figure_out_type(value: Any) -> types.GenericType:
|
||||
return Set[unionize(*(figure_out_type(v) for v in value))]
|
||||
if isinstance(value, tuple):
|
||||
return Tuple[unionize(*(figure_out_type(v) for v in value)), ...]
|
||||
if isinstance(value, dict):
|
||||
return Dict[
|
||||
if isinstance(value, Mapping):
|
||||
return Mapping[
|
||||
unionize(*(figure_out_type(k) for k in value)),
|
||||
unionize(*(figure_out_type(v) for v in value.values())),
|
||||
]
|
||||
@ -1859,19 +1833,14 @@ class ComputedVar(Var[RETURN_TYPE]):
|
||||
|
||||
Raises:
|
||||
TypeError: If the computed var dependencies are not Var instances or var names.
|
||||
UntypedComputedVarError: If the computed var is untyped.
|
||||
"""
|
||||
hint = kwargs.pop("return_type", None) or get_type_hints(fget).get(
|
||||
"return", Any
|
||||
)
|
||||
|
||||
if hint is Any:
|
||||
console.deprecate(
|
||||
"untyped-computed-var",
|
||||
"ComputedVar should have a return type annotation.",
|
||||
"0.6.5",
|
||||
"0.7.0",
|
||||
)
|
||||
|
||||
raise UntypedComputedVarError(var_name=fget.__name__)
|
||||
kwargs.setdefault("_js_expr", fget.__name__)
|
||||
kwargs.setdefault("_var_type", hint)
|
||||
|
||||
@ -1944,6 +1913,7 @@ class ComputedVar(Var[RETURN_TYPE]):
|
||||
"_var_data": kwargs.pop(
|
||||
"_var_data", VarData.merge(self._var_data, merge_var_data)
|
||||
),
|
||||
"return_type": kwargs.pop("return_type", self._var_type),
|
||||
}
|
||||
|
||||
if kwargs:
|
||||
@ -2002,10 +1972,10 @@ class ComputedVar(Var[RETURN_TYPE]):
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
self: ComputedVar[dict[DICT_KEY, DICT_VAL]],
|
||||
self: ComputedVar[Mapping[DICT_KEY, DICT_VAL]],
|
||||
instance: None,
|
||||
owner: Type,
|
||||
) -> ObjectVar[dict[DICT_KEY, DICT_VAL]]: ...
|
||||
) -> ObjectVar[Mapping[DICT_KEY, DICT_VAL]]: ...
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
@ -2078,12 +2048,9 @@ class ComputedVar(Var[RETURN_TYPE]):
|
||||
value = getattr(instance, self._cache_attr)
|
||||
|
||||
if not _isinstance(value, self._var_type):
|
||||
console.deprecate(
|
||||
"mismatched-computed-var-return",
|
||||
f"Computed var {type(instance).__name__}.{self._js_expr} returned value of type {type(value)}, "
|
||||
f"expected {self._var_type}. This might cause unexpected behavior.",
|
||||
"0.6.5",
|
||||
"0.7.0",
|
||||
console.error(
|
||||
f"Computed var '{type(instance).__name__}.{self._js_expr}' must return"
|
||||
f" type '{self._var_type}', got '{type(value)}'."
|
||||
)
|
||||
|
||||
return value
|
||||
@ -2915,11 +2882,14 @@ V = TypeVar("V")
|
||||
|
||||
BASE_TYPE = TypeVar("BASE_TYPE", bound=Base)
|
||||
|
||||
FIELD_TYPE = TypeVar("FIELD_TYPE")
|
||||
MAPPING_TYPE = TypeVar("MAPPING_TYPE", bound=Mapping)
|
||||
|
||||
class Field(Generic[T]):
|
||||
|
||||
class Field(Generic[FIELD_TYPE]):
|
||||
"""Shadow class for Var to allow for type hinting in the IDE."""
|
||||
|
||||
def __set__(self, instance, value: T):
|
||||
def __set__(self, instance, value: FIELD_TYPE):
|
||||
"""Set the Var.
|
||||
|
||||
Args:
|
||||
@ -2931,7 +2901,9 @@ class Field(Generic[T]):
|
||||
def __get__(self: Field[bool], instance: None, owner) -> BooleanVar: ...
|
||||
|
||||
@overload
|
||||
def __get__(self: Field[int], instance: None, owner) -> NumberVar: ...
|
||||
def __get__(
|
||||
self: Field[int] | Field[float] | Field[int | float], instance: None, owner
|
||||
) -> NumberVar: ...
|
||||
|
||||
@overload
|
||||
def __get__(self: Field[str], instance: None, owner) -> StringVar: ...
|
||||
@ -2948,8 +2920,8 @@ class Field(Generic[T]):
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
self: Field[Dict[str, V]], instance: None, owner
|
||||
) -> ObjectVar[Dict[str, V]]: ...
|
||||
self: Field[MAPPING_TYPE], instance: None, owner
|
||||
) -> ObjectVar[MAPPING_TYPE]: ...
|
||||
|
||||
@overload
|
||||
def __get__(
|
||||
@ -2957,10 +2929,10 @@ class Field(Generic[T]):
|
||||
) -> ObjectVar[BASE_TYPE]: ...
|
||||
|
||||
@overload
|
||||
def __get__(self, instance: None, owner) -> Var[T]: ...
|
||||
def __get__(self, instance: None, owner) -> Var[FIELD_TYPE]: ...
|
||||
|
||||
@overload
|
||||
def __get__(self, instance, owner) -> T: ...
|
||||
def __get__(self, instance, owner) -> FIELD_TYPE: ...
|
||||
|
||||
def __get__(self, instance, owner): # type: ignore
|
||||
"""Get the Var.
|
||||
@ -2971,7 +2943,7 @@ class Field(Generic[T]):
|
||||
"""
|
||||
|
||||
|
||||
def field(value: T) -> Field[T]:
|
||||
def field(value: FIELD_TYPE) -> Field[FIELD_TYPE]:
|
||||
"""Create a Field with a value.
|
||||
|
||||
Args:
|
||||
|
@ -8,8 +8,8 @@ import typing
|
||||
from inspect import isclass
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Mapping,
|
||||
NoReturn,
|
||||
Tuple,
|
||||
Type,
|
||||
@ -19,6 +19,8 @@ from typing import (
|
||||
overload,
|
||||
)
|
||||
|
||||
from typing_extensions import is_typeddict
|
||||
|
||||
from reflex.utils import types
|
||||
from reflex.utils.exceptions import VarAttributeError
|
||||
from reflex.utils.types import GenericType, get_attribute_access_type, get_origin
|
||||
@ -36,7 +38,7 @@ from .base import (
|
||||
from .number import BooleanVar, NumberVar, raise_unsupported_operand_types
|
||||
from .sequence import ArrayVar, StringVar
|
||||
|
||||
OBJECT_TYPE = TypeVar("OBJECT_TYPE")
|
||||
OBJECT_TYPE = TypeVar("OBJECT_TYPE", covariant=True)
|
||||
|
||||
KEY_TYPE = TypeVar("KEY_TYPE")
|
||||
VALUE_TYPE = TypeVar("VALUE_TYPE")
|
||||
@ -46,7 +48,7 @@ ARRAY_INNER_TYPE = TypeVar("ARRAY_INNER_TYPE")
|
||||
OTHER_KEY_TYPE = TypeVar("OTHER_KEY_TYPE")
|
||||
|
||||
|
||||
class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
||||
class ObjectVar(Var[OBJECT_TYPE], python_types=Mapping):
|
||||
"""Base class for immutable object vars."""
|
||||
|
||||
def _key_type(self) -> Type:
|
||||
@ -59,7 +61,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
||||
|
||||
@overload
|
||||
def _value_type(
|
||||
self: ObjectVar[Dict[Any, VALUE_TYPE]],
|
||||
self: ObjectVar[Mapping[Any, VALUE_TYPE]],
|
||||
) -> Type[VALUE_TYPE]: ...
|
||||
|
||||
@overload
|
||||
@ -74,7 +76,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
||||
fixed_type = get_origin(self._var_type) or self._var_type
|
||||
if not isclass(fixed_type):
|
||||
return Any
|
||||
args = get_args(self._var_type) if issubclass(fixed_type, dict) else ()
|
||||
args = get_args(self._var_type) if issubclass(fixed_type, Mapping) else ()
|
||||
return args[1] if args else Any
|
||||
|
||||
def keys(self) -> ArrayVar[List[str]]:
|
||||
@ -87,7 +89,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
||||
|
||||
@overload
|
||||
def values(
|
||||
self: ObjectVar[Dict[Any, VALUE_TYPE]],
|
||||
self: ObjectVar[Mapping[Any, VALUE_TYPE]],
|
||||
) -> ArrayVar[List[VALUE_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
@ -103,7 +105,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
||||
|
||||
@overload
|
||||
def entries(
|
||||
self: ObjectVar[Dict[Any, VALUE_TYPE]],
|
||||
self: ObjectVar[Mapping[Any, VALUE_TYPE]],
|
||||
) -> ArrayVar[List[Tuple[str, VALUE_TYPE]]]: ...
|
||||
|
||||
@overload
|
||||
@ -133,49 +135,55 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
||||
# NoReturn is used here to catch when key value is Any
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[Any, NoReturn]],
|
||||
self: ObjectVar[Mapping[Any, NoReturn]],
|
||||
key: Var | Any,
|
||||
) -> Var: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: (ObjectVar[Mapping[Any, bool]]),
|
||||
key: Var | Any,
|
||||
) -> BooleanVar: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: (
|
||||
ObjectVar[Dict[Any, int]]
|
||||
| ObjectVar[Dict[Any, float]]
|
||||
| ObjectVar[Dict[Any, int | float]]
|
||||
ObjectVar[Mapping[Any, int]]
|
||||
| ObjectVar[Mapping[Any, float]]
|
||||
| ObjectVar[Mapping[Any, int | float]]
|
||||
),
|
||||
key: Var | Any,
|
||||
) -> NumberVar: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[Any, str]],
|
||||
self: ObjectVar[Mapping[Any, str]],
|
||||
key: Var | Any,
|
||||
) -> StringVar: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[Any, list[ARRAY_INNER_TYPE]]],
|
||||
self: ObjectVar[Mapping[Any, list[ARRAY_INNER_TYPE]]],
|
||||
key: Var | Any,
|
||||
) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[Any, set[ARRAY_INNER_TYPE]]],
|
||||
self: ObjectVar[Mapping[Any, set[ARRAY_INNER_TYPE]]],
|
||||
key: Var | Any,
|
||||
) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[Any, tuple[ARRAY_INNER_TYPE, ...]]],
|
||||
self: ObjectVar[Mapping[Any, tuple[ARRAY_INNER_TYPE, ...]]],
|
||||
key: Var | Any,
|
||||
) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(
|
||||
self: ObjectVar[Dict[Any, dict[OTHER_KEY_TYPE, VALUE_TYPE]]],
|
||||
self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]],
|
||||
key: Var | Any,
|
||||
) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
|
||||
) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
|
||||
|
||||
def __getitem__(self, key: Var | Any) -> Var:
|
||||
"""Get an item from the object.
|
||||
@ -195,49 +203,49 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
||||
# NoReturn is used here to catch when key value is Any
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[Any, NoReturn]],
|
||||
self: ObjectVar[Mapping[Any, NoReturn]],
|
||||
name: str,
|
||||
) -> Var: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: (
|
||||
ObjectVar[Dict[Any, int]]
|
||||
| ObjectVar[Dict[Any, float]]
|
||||
| ObjectVar[Dict[Any, int | float]]
|
||||
ObjectVar[Mapping[Any, int]]
|
||||
| ObjectVar[Mapping[Any, float]]
|
||||
| ObjectVar[Mapping[Any, int | float]]
|
||||
),
|
||||
name: str,
|
||||
) -> NumberVar: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[Any, str]],
|
||||
self: ObjectVar[Mapping[Any, str]],
|
||||
name: str,
|
||||
) -> StringVar: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[Any, list[ARRAY_INNER_TYPE]]],
|
||||
self: ObjectVar[Mapping[Any, list[ARRAY_INNER_TYPE]]],
|
||||
name: str,
|
||||
) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[Any, set[ARRAY_INNER_TYPE]]],
|
||||
self: ObjectVar[Mapping[Any, set[ARRAY_INNER_TYPE]]],
|
||||
name: str,
|
||||
) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[Any, tuple[ARRAY_INNER_TYPE, ...]]],
|
||||
self: ObjectVar[Mapping[Any, tuple[ARRAY_INNER_TYPE, ...]]],
|
||||
name: str,
|
||||
) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
self: ObjectVar[Dict[Any, dict[OTHER_KEY_TYPE, VALUE_TYPE]]],
|
||||
self: ObjectVar[Mapping[Any, Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]],
|
||||
name: str,
|
||||
) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
|
||||
) -> ObjectVar[Mapping[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
|
||||
|
||||
@overload
|
||||
def __getattr__(
|
||||
@ -266,8 +274,11 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
||||
var_type = get_args(var_type)[0]
|
||||
|
||||
fixed_type = var_type if isclass(var_type) else get_origin(var_type)
|
||||
if (isclass(fixed_type) and not issubclass(fixed_type, dict)) or (
|
||||
fixed_type in types.UnionTypes
|
||||
|
||||
if (
|
||||
(isclass(fixed_type) and not issubclass(fixed_type, Mapping))
|
||||
or (fixed_type in types.UnionTypes)
|
||||
or is_typeddict(fixed_type)
|
||||
):
|
||||
attribute_type = get_attribute_access_type(var_type, name)
|
||||
if attribute_type is None:
|
||||
@ -299,7 +310,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
||||
class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar):
|
||||
"""Base class for immutable literal object vars."""
|
||||
|
||||
_var_value: Dict[Union[Var, Any], Union[Var, Any]] = dataclasses.field(
|
||||
_var_value: Mapping[Union[Var, Any], Union[Var, Any]] = dataclasses.field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
@ -383,7 +394,7 @@ class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar):
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
_var_value: dict,
|
||||
_var_value: Mapping,
|
||||
_var_type: Type[OBJECT_TYPE] | None = None,
|
||||
_var_data: VarData | None = None,
|
||||
) -> LiteralObjectVar[OBJECT_TYPE]:
|
||||
@ -466,7 +477,7 @@ def object_merge_operation(lhs: ObjectVar, rhs: ObjectVar):
|
||||
"""
|
||||
return var_operation_return(
|
||||
js_expression=f"({{...{lhs}, ...{rhs}}})",
|
||||
var_type=Dict[
|
||||
var_type=Mapping[
|
||||
Union[lhs._key_type(), rhs._key_type()],
|
||||
Union[lhs._value_type(), rhs._value_type()],
|
||||
],
|
||||
|
@ -987,7 +987,7 @@ class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(list, tuple, set)):
|
||||
raise_unsupported_operand_types("[]", (type(self), type(i)))
|
||||
return array_item_operation(self, i)
|
||||
|
||||
def length(self) -> NumberVar:
|
||||
def length(self) -> NumberVar[int]:
|
||||
"""Get the length of the array.
|
||||
|
||||
Returns:
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM python:3.9
|
||||
FROM python:3.10
|
||||
|
||||
ARG USERNAME=kerrigan
|
||||
RUN useradd -m $USERNAME
|
||||
|
@ -16,7 +16,7 @@ from .utils import SessionStorage
|
||||
def CallScript():
|
||||
"""A test app for browser javascript integration."""
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Union
|
||||
from typing import Optional, Union
|
||||
|
||||
import reflex as rx
|
||||
|
||||
@ -43,15 +43,17 @@ def CallScript():
|
||||
external_scripts = inline_scripts.replace("inline", "external")
|
||||
|
||||
class CallScriptState(rx.State):
|
||||
results: List[Optional[Union[str, Dict, List]]] = []
|
||||
inline_counter: int = 0
|
||||
external_counter: int = 0
|
||||
results: rx.Field[list[Optional[Union[str, dict, list]]]] = rx.field([])
|
||||
inline_counter: rx.Field[int] = rx.field(0)
|
||||
external_counter: rx.Field[int] = rx.field(0)
|
||||
value: str = "Initial"
|
||||
last_result: str = ""
|
||||
last_result: int = 0
|
||||
|
||||
@rx.event
|
||||
def call_script_callback(self, result):
|
||||
self.results.append(result)
|
||||
|
||||
@rx.event
|
||||
def call_script_callback_other_arg(self, result, other_arg):
|
||||
self.results.append([other_arg, result])
|
||||
|
||||
@ -91,7 +93,7 @@ def CallScript():
|
||||
def call_script_inline_return_lambda(self):
|
||||
return rx.call_script(
|
||||
"inline2()",
|
||||
callback=lambda result: CallScriptState.call_script_callback_other_arg( # type: ignore
|
||||
callback=lambda result: CallScriptState.call_script_callback_other_arg(
|
||||
result, "lambda"
|
||||
),
|
||||
)
|
||||
@ -100,7 +102,7 @@ def CallScript():
|
||||
def get_inline_counter(self):
|
||||
return rx.call_script(
|
||||
"inline_counter",
|
||||
callback=CallScriptState.set_inline_counter, # type: ignore
|
||||
callback=CallScriptState.setvar("inline_counter"),
|
||||
)
|
||||
|
||||
@rx.event
|
||||
@ -139,7 +141,7 @@ def CallScript():
|
||||
def call_script_external_return_lambda(self):
|
||||
return rx.call_script(
|
||||
"external2()",
|
||||
callback=lambda result: CallScriptState.call_script_callback_other_arg( # type: ignore
|
||||
callback=lambda result: CallScriptState.call_script_callback_other_arg(
|
||||
result, "lambda"
|
||||
),
|
||||
)
|
||||
@ -148,28 +150,28 @@ def CallScript():
|
||||
def get_external_counter(self):
|
||||
return rx.call_script(
|
||||
"external_counter",
|
||||
callback=CallScriptState.set_external_counter, # type: ignore
|
||||
callback=CallScriptState.setvar("external_counter"),
|
||||
)
|
||||
|
||||
@rx.event
|
||||
def call_with_var_f_string(self):
|
||||
return rx.call_script(
|
||||
f"{rx.Var('inline_counter')} + {rx.Var('external_counter')}",
|
||||
callback=CallScriptState.set_last_result, # type: ignore
|
||||
callback=CallScriptState.setvar("last_result"),
|
||||
)
|
||||
|
||||
@rx.event
|
||||
def call_with_var_str_cast(self):
|
||||
return rx.call_script(
|
||||
f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}",
|
||||
callback=CallScriptState.set_last_result, # type: ignore
|
||||
callback=CallScriptState.setvar("last_result"),
|
||||
)
|
||||
|
||||
@rx.event
|
||||
def call_with_var_f_string_wrapped(self):
|
||||
return rx.call_script(
|
||||
rx.Var(f"{rx.Var('inline_counter')} + {rx.Var('external_counter')}"),
|
||||
callback=CallScriptState.set_last_result, # type: ignore
|
||||
callback=CallScriptState.setvar("last_result"),
|
||||
)
|
||||
|
||||
@rx.event
|
||||
@ -178,7 +180,7 @@ def CallScript():
|
||||
rx.Var(
|
||||
f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}"
|
||||
),
|
||||
callback=CallScriptState.set_last_result, # type: ignore
|
||||
callback=CallScriptState.setvar("last_result"),
|
||||
)
|
||||
|
||||
@rx.event
|
||||
@ -193,17 +195,17 @@ def CallScript():
|
||||
def index():
|
||||
return rx.vstack(
|
||||
rx.input(
|
||||
value=CallScriptState.inline_counter.to(str), # type: ignore
|
||||
value=CallScriptState.inline_counter.to(str),
|
||||
id="inline_counter",
|
||||
read_only=True,
|
||||
),
|
||||
rx.input(
|
||||
value=CallScriptState.external_counter.to(str), # type: ignore
|
||||
value=CallScriptState.external_counter.to(str),
|
||||
id="external_counter",
|
||||
read_only=True,
|
||||
),
|
||||
rx.text_area(
|
||||
value=CallScriptState.results.to_string(), # type: ignore
|
||||
value=CallScriptState.results.to_string(),
|
||||
id="results",
|
||||
read_only=True,
|
||||
),
|
||||
@ -273,7 +275,7 @@ def CallScript():
|
||||
CallScriptState.value,
|
||||
on_click=rx.call_script(
|
||||
"'updated'",
|
||||
callback=CallScriptState.set_value, # type: ignore
|
||||
callback=CallScriptState.setvar("value"),
|
||||
),
|
||||
id="update_value",
|
||||
),
|
||||
@ -282,7 +284,7 @@ def CallScript():
|
||||
value=CallScriptState.last_result,
|
||||
id="last_result",
|
||||
read_only=True,
|
||||
on_click=CallScriptState.set_last_result(""), # type: ignore
|
||||
on_click=CallScriptState.setvar("last_result", 0),
|
||||
),
|
||||
rx.button(
|
||||
"call_with_var_f_string",
|
||||
@ -308,7 +310,7 @@ def CallScript():
|
||||
"call_with_var_f_string_inline",
|
||||
on_click=rx.call_script(
|
||||
f"{rx.Var('inline_counter')} + {CallScriptState.last_result}",
|
||||
callback=CallScriptState.set_last_result, # type: ignore
|
||||
callback=CallScriptState.setvar("last_result"),
|
||||
),
|
||||
id="call_with_var_f_string_inline",
|
||||
),
|
||||
@ -316,7 +318,7 @@ def CallScript():
|
||||
"call_with_var_str_cast_inline",
|
||||
on_click=rx.call_script(
|
||||
f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}",
|
||||
callback=CallScriptState.set_last_result, # type: ignore
|
||||
callback=CallScriptState.setvar("last_result"),
|
||||
),
|
||||
id="call_with_var_str_cast_inline",
|
||||
),
|
||||
@ -326,7 +328,7 @@ def CallScript():
|
||||
rx.Var(
|
||||
f"{rx.Var('inline_counter')} + {CallScriptState.last_result}"
|
||||
),
|
||||
callback=CallScriptState.set_last_result, # type: ignore
|
||||
callback=CallScriptState.setvar("last_result"),
|
||||
),
|
||||
id="call_with_var_f_string_wrapped_inline",
|
||||
),
|
||||
@ -336,7 +338,7 @@ def CallScript():
|
||||
rx.Var(
|
||||
f"{rx.Var('inline_counter')!s} + {rx.Var('external_counter')!s}"
|
||||
),
|
||||
callback=CallScriptState.set_last_result, # type: ignore
|
||||
callback=CallScriptState.setvar("last_result"),
|
||||
),
|
||||
id="call_with_var_str_cast_wrapped_inline",
|
||||
),
|
||||
@ -483,7 +485,7 @@ def test_call_script_w_var(
|
||||
"""
|
||||
assert_token(driver)
|
||||
last_result = driver.find_element(By.ID, "last_result")
|
||||
assert last_result.get_attribute("value") == ""
|
||||
assert last_result.get_attribute("value") == "0"
|
||||
|
||||
inline_return_button = driver.find_element(By.ID, "inline_return")
|
||||
|
||||
|
@ -33,18 +33,18 @@ def ClientSide():
|
||||
class ClientSideSubState(ClientSideState):
|
||||
# cookies with default settings
|
||||
c1: str = rx.Cookie()
|
||||
c2: rx.Cookie = "c2 default" # type: ignore
|
||||
c2: str = rx.Cookie("c2 default")
|
||||
|
||||
# cookies with custom settings
|
||||
c3: str = rx.Cookie(max_age=2) # expires after 2 second
|
||||
c4: rx.Cookie = rx.Cookie(same_site="strict")
|
||||
c4: str = rx.Cookie(same_site="strict")
|
||||
c5: str = rx.Cookie(path="/foo/") # only accessible on `/foo/`
|
||||
c6: str = rx.Cookie(name="c6")
|
||||
c7: str = rx.Cookie("c7 default")
|
||||
|
||||
# local storage with default settings
|
||||
l1: str = rx.LocalStorage()
|
||||
l2: rx.LocalStorage = "l2 default" # type: ignore
|
||||
l2: str = rx.LocalStorage("l2 default")
|
||||
|
||||
# local storage with custom settings
|
||||
l3: str = rx.LocalStorage(name="l3")
|
||||
@ -56,7 +56,7 @@ def ClientSide():
|
||||
|
||||
# Session storage
|
||||
s1: str = rx.SessionStorage()
|
||||
s2: rx.SessionStorage = "s2 default" # type: ignore
|
||||
s2: str = rx.SessionStorage("s2 default")
|
||||
s3: str = rx.SessionStorage(name="s3")
|
||||
|
||||
def set_l6(self, my_param: str):
|
||||
@ -87,13 +87,13 @@ def ClientSide():
|
||||
rx.input(
|
||||
placeholder="state var",
|
||||
value=ClientSideState.state_var,
|
||||
on_change=ClientSideState.set_state_var, # type: ignore
|
||||
on_change=ClientSideState.setvar("state_var"),
|
||||
id="state_var",
|
||||
),
|
||||
rx.input(
|
||||
placeholder="input value",
|
||||
value=ClientSideState.input_value,
|
||||
on_change=ClientSideState.set_input_value, # type: ignore
|
||||
on_change=ClientSideState.setvar("input_value"),
|
||||
id="input_value",
|
||||
),
|
||||
rx.button(
|
||||
|
@ -37,19 +37,6 @@ def test_shared_asset() -> None:
|
||||
assert not Path(Path.cwd() / "assets/external").exists()
|
||||
|
||||
|
||||
def test_deprecated_x_asset(capsys) -> None:
|
||||
"""Test that the deprecated asset function raises a warning.
|
||||
|
||||
Args:
|
||||
capsys: Pytest fixture that captures stdout and stderr.
|
||||
"""
|
||||
assert rx.asset("custom_script.js", shared=True) == rx._x.asset("custom_script.js")
|
||||
assert (
|
||||
"DeprecationWarning: rx._x.asset has been deprecated in version 0.6.6"
|
||||
in capsys.readouterr().out
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path,shared",
|
||||
[
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, Tuple
|
||||
from typing import List, Mapping, Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
@ -67,7 +67,7 @@ def test_match_components():
|
||||
assert fourth_return_value_render["children"][0]["contents"] == '{"fourth value"}'
|
||||
|
||||
assert match_cases[4][0]._js_expr == '({ ["foo"] : "bar" })'
|
||||
assert match_cases[4][0]._var_type == Dict[str, str]
|
||||
assert match_cases[4][0]._var_type == Mapping[str, str]
|
||||
fifth_return_value_render = match_cases[4][1].render()
|
||||
assert fifth_return_value_render["name"] == "RadixThemesText"
|
||||
assert fifth_return_value_render["children"][0]["contents"] == '{"fifth value"}'
|
||||
|
@ -1,7 +1,5 @@
|
||||
"""Data display component tests fixtures."""
|
||||
|
||||
from typing import List
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
@ -54,11 +52,11 @@ def data_table_state3():
|
||||
"""
|
||||
|
||||
class DataTableState(BaseState):
|
||||
_data: List = []
|
||||
_columns: List = ["col1", "col2"]
|
||||
_data: list = []
|
||||
_columns: list = ["col1", "col2"]
|
||||
|
||||
@rx.var
|
||||
def data(self) -> List:
|
||||
def data(self) -> list:
|
||||
return self._data
|
||||
|
||||
@rx.var
|
||||
@ -77,15 +75,15 @@ def data_table_state4():
|
||||
"""
|
||||
|
||||
class DataTableState(BaseState):
|
||||
_data: List = []
|
||||
_columns: List = ["col1", "col2"]
|
||||
_data: list = []
|
||||
_columns: list[str] = ["col1", "col2"]
|
||||
|
||||
@rx.var
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@rx.var
|
||||
def columns(self) -> List:
|
||||
def columns(self) -> list:
|
||||
return self._columns
|
||||
|
||||
return DataTableState
|
||||
|
@ -4,6 +4,7 @@ import pytest
|
||||
import reflex as rx
|
||||
from reflex.components.gridjs.datatable import DataTable
|
||||
from reflex.utils import types
|
||||
from reflex.utils.exceptions import UntypedComputedVarError
|
||||
from reflex.utils.serializers import serialize, serialize_dataframe
|
||||
|
||||
|
||||
@ -75,17 +76,17 @@ def test_invalid_props(props):
|
||||
[
|
||||
(
|
||||
"data_table_state2",
|
||||
"Annotation of the computed var assigned to the data field should be provided.",
|
||||
"Computed var 'data' must have a type annotation.",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"data_table_state3",
|
||||
"Annotation of the computed var assigned to the column field should be provided.",
|
||||
"Computed var 'columns' must have a type annotation.",
|
||||
False,
|
||||
),
|
||||
(
|
||||
"data_table_state4",
|
||||
"Annotation of the computed var assigned to the data field should be provided.",
|
||||
"Computed var 'data' must have a type annotation.",
|
||||
False,
|
||||
),
|
||||
],
|
||||
@ -99,7 +100,7 @@ def test_computed_var_without_annotation(fixture, request, err_msg, is_data_fram
|
||||
err_msg: expected error message.
|
||||
is_data_frame: whether data field is a pandas dataframe.
|
||||
"""
|
||||
with pytest.raises(ValueError) as err:
|
||||
with pytest.raises(UntypedComputedVarError) as err:
|
||||
if is_data_frame:
|
||||
DataTable.create(data=request.getfixturevalue(fixture).data)
|
||||
else:
|
||||
|
@ -19,6 +19,7 @@ from reflex.constants import EventTriggers
|
||||
from reflex.event import (
|
||||
EventChain,
|
||||
EventHandler,
|
||||
JavascriptInputEvent,
|
||||
input_event,
|
||||
no_args_event_spec,
|
||||
parse_args_spec,
|
||||
@ -27,7 +28,11 @@ from reflex.event import (
|
||||
from reflex.state import BaseState
|
||||
from reflex.style import Style
|
||||
from reflex.utils import imports
|
||||
from reflex.utils.exceptions import ChildrenTypeError, EventFnArgMismatchError
|
||||
from reflex.utils.exceptions import (
|
||||
ChildrenTypeError,
|
||||
EventFnArgMismatchError,
|
||||
EventHandlerArgTypeMismatchError,
|
||||
)
|
||||
from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import LiteralVar, Var
|
||||
@ -94,11 +99,14 @@ def component2() -> Type[Component]:
|
||||
A test component.
|
||||
"""
|
||||
|
||||
def on_prop_event_spec(e0: Any):
|
||||
return [e0]
|
||||
|
||||
class TestComponent2(Component):
|
||||
# A test list prop.
|
||||
arr: Var[List[str]]
|
||||
|
||||
on_prop_event: EventHandler[lambda e0: [e0]]
|
||||
on_prop_event: EventHandler[on_prop_event_spec]
|
||||
|
||||
def get_event_triggers(self) -> Dict[str, Any]:
|
||||
"""Test controlled triggers.
|
||||
@ -818,10 +826,14 @@ def test_component_create_unpack_tuple_child(test_component, element, expected):
|
||||
assert fragment_wrapper.render() == expected
|
||||
|
||||
|
||||
class _Obj(Base):
|
||||
custom: int = 0
|
||||
|
||||
|
||||
class C1State(BaseState):
|
||||
"""State for testing C1 component."""
|
||||
|
||||
def mock_handler(self, _e, _bravo, _charlie):
|
||||
def mock_handler(self, _e: JavascriptInputEvent, _bravo: dict, _charlie: _Obj):
|
||||
"""Mock handler."""
|
||||
pass
|
||||
|
||||
@ -829,10 +841,12 @@ class C1State(BaseState):
|
||||
def test_component_event_trigger_arbitrary_args():
|
||||
"""Test that we can define arbitrary types for the args of an event trigger."""
|
||||
|
||||
class Obj(Base):
|
||||
custom: int = 0
|
||||
|
||||
def on_foo_spec(_e, alpha: str, bravo: Dict[str, Any], charlie: Obj):
|
||||
def on_foo_spec(
|
||||
_e: Var[JavascriptInputEvent],
|
||||
alpha: Var[str],
|
||||
bravo: dict[str, Any],
|
||||
charlie: Var[_Obj],
|
||||
):
|
||||
return [_e.target.value, bravo["nested"], charlie.custom + 42]
|
||||
|
||||
class C1(Component):
|
||||
@ -845,13 +859,7 @@ def test_component_event_trigger_arbitrary_args():
|
||||
"on_foo": on_foo_spec,
|
||||
}
|
||||
|
||||
comp = C1.create(on_foo=C1State.mock_handler)
|
||||
|
||||
assert comp.render()["props"][0] == (
|
||||
"onFoo={((__e, _alpha, _bravo, _charlie) => (addEvents("
|
||||
f'[(Event("{C1State.get_full_name()}.mock_handler", ({{ ["_e"] : __e["target"]["value"], ["_bravo"] : _bravo["nested"], ["_charlie"] : (_charlie["custom"] + 42) }}), ({{ }})))], '
|
||||
"[__e, _alpha, _bravo, _charlie], ({ }))))}"
|
||||
)
|
||||
C1.create(on_foo=C1State.mock_handler)
|
||||
|
||||
|
||||
def test_create_custom_component(my_component):
|
||||
@ -917,21 +925,20 @@ def test_invalid_event_handler_args(component2, test_state):
|
||||
on_click=[test_state.do_something_arg, test_state.do_something]
|
||||
)
|
||||
|
||||
# Enable when 0.7.0 happens
|
||||
# # Event Handler types must match
|
||||
# with pytest.raises(EventHandlerArgTypeMismatch):
|
||||
# component2.create(
|
||||
# on_user_visited_count_changed=test_state.do_something_with_bool # noqa: ERA001 RUF100
|
||||
# ) # noqa: ERA001 RUF100
|
||||
# with pytest.raises(EventHandlerArgTypeMismatch):
|
||||
# component2.create(on_user_list_changed=test_state.do_something_with_int) #noqa: ERA001
|
||||
# with pytest.raises(EventHandlerArgTypeMismatch):
|
||||
# component2.create(on_user_list_changed=test_state.do_something_with_list_int) #noqa: ERA001
|
||||
with pytest.raises(EventHandlerArgTypeMismatchError):
|
||||
component2.create(
|
||||
on_user_visited_count_changed=test_state.do_something_with_bool
|
||||
)
|
||||
with pytest.raises(EventHandlerArgTypeMismatchError):
|
||||
component2.create(on_user_list_changed=test_state.do_something_with_int)
|
||||
with pytest.raises(EventHandlerArgTypeMismatchError):
|
||||
component2.create(on_user_list_changed=test_state.do_something_with_list_int)
|
||||
|
||||
# component2.create(on_open=test_state.do_something_with_int) #noqa: ERA001
|
||||
# component2.create(on_open=test_state.do_something_with_bool) #noqa: ERA001
|
||||
# component2.create(on_user_visited_count_changed=test_state.do_something_with_int) #noqa: ERA001
|
||||
# component2.create(on_user_list_changed=test_state.do_something_with_list_str) #noqa: ERA001
|
||||
component2.create(on_open=test_state.do_something_with_int)
|
||||
component2.create(on_open=test_state.do_something_with_bool)
|
||||
component2.create(on_user_visited_count_changed=test_state.do_something_with_int)
|
||||
component2.create(on_user_list_changed=test_state.do_something_with_list_str)
|
||||
|
||||
# lambda cannot return weird values.
|
||||
with pytest.raises(ValueError):
|
||||
@ -1801,21 +1808,15 @@ def test_custom_component_declare_event_handlers_in_fields():
|
||||
"""
|
||||
return {
|
||||
**super().get_event_triggers(),
|
||||
"on_a": lambda e0: [e0],
|
||||
"on_b": input_event,
|
||||
"on_c": lambda e0: [],
|
||||
"on_d": lambda: [],
|
||||
"on_e": lambda: [],
|
||||
"on_f": lambda a, b, c: [c, b, a],
|
||||
}
|
||||
|
||||
class TestComponent(Component):
|
||||
on_a: EventHandler[lambda e0: [e0]]
|
||||
on_b: EventHandler[input_event]
|
||||
on_c: EventHandler[no_args_event_spec]
|
||||
on_d: EventHandler[no_args_event_spec]
|
||||
on_e: EventHandler
|
||||
on_f: EventHandler[lambda a, b, c: [c, b, a]]
|
||||
|
||||
custom_component = ReferenceComponent.create()
|
||||
test_component = TestComponent.create()
|
||||
|
@ -199,16 +199,15 @@ def test_event_redirect(input, output):
|
||||
input: The input for running the test.
|
||||
output: The expected output to validate the test.
|
||||
"""
|
||||
path, external, replace = input
|
||||
path, is_external, replace = input
|
||||
kwargs = {}
|
||||
if external is not None:
|
||||
kwargs["external"] = external
|
||||
if is_external is not None:
|
||||
kwargs["is_external"] = is_external
|
||||
if replace is not None:
|
||||
kwargs["replace"] = replace
|
||||
spec = event.redirect(path, **kwargs)
|
||||
assert isinstance(spec, EventSpec)
|
||||
assert spec.handler.fn.__qualname__ == "_redirect"
|
||||
|
||||
assert format.format_event(spec) == output
|
||||
|
||||
|
||||
|
@ -1144,7 +1144,7 @@ def test_child_state():
|
||||
|
||||
class ChildState(MainState):
|
||||
@computed_var
|
||||
def rendered_var(self):
|
||||
def rendered_var(self) -> int:
|
||||
return self.v
|
||||
|
||||
ms = MainState()
|
||||
@ -1421,7 +1421,7 @@ def test_computed_var_dependencies():
|
||||
return self.testprop
|
||||
|
||||
@rx.var
|
||||
def comp_w(self):
|
||||
def comp_w(self) -> Callable[[], int]:
|
||||
"""Nested lambda.
|
||||
|
||||
Returns:
|
||||
@ -1430,7 +1430,7 @@ def test_computed_var_dependencies():
|
||||
return lambda: self.w
|
||||
|
||||
@rx.var
|
||||
def comp_x(self):
|
||||
def comp_x(self) -> Callable[[], int]:
|
||||
"""Nested function.
|
||||
|
||||
Returns:
|
||||
@ -1443,7 +1443,7 @@ def test_computed_var_dependencies():
|
||||
return _
|
||||
|
||||
@rx.var
|
||||
def comp_y(self) -> List[int]:
|
||||
def comp_y(self) -> list[int]:
|
||||
"""Comprehension iterating over attribute.
|
||||
|
||||
Returns:
|
||||
@ -3128,7 +3128,7 @@ async def test_get_state_from_sibling_not_cached(mock_app: rx.App, token: str):
|
||||
child3_var: int = 0
|
||||
|
||||
@rx.var(cache=False)
|
||||
def v(self):
|
||||
def v(self) -> None:
|
||||
pass
|
||||
|
||||
class Grandchild3(Child3):
|
||||
|
@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
from typing import Any, Mapping
|
||||
|
||||
import pytest
|
||||
|
||||
@ -379,7 +379,7 @@ class StyleState(rx.State):
|
||||
{
|
||||
"css": Var(
|
||||
_js_expr=f'({{ ["color"] : ("dark"+{StyleState.color}) }})'
|
||||
).to(Dict[str, str])
|
||||
).to(Mapping[str, str])
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -2,7 +2,7 @@ import json
|
||||
import math
|
||||
import sys
|
||||
import typing
|
||||
from typing import Dict, List, Optional, Set, Tuple, Union, cast
|
||||
from typing import Dict, List, Mapping, Optional, Set, Tuple, Union, cast
|
||||
|
||||
import pytest
|
||||
from pandas import DataFrame
|
||||
@ -11,7 +11,10 @@ import reflex as rx
|
||||
from reflex.base import Base
|
||||
from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
|
||||
from reflex.state import BaseState
|
||||
from reflex.utils.exceptions import PrimitiveUnserializableToJSONError
|
||||
from reflex.utils.exceptions import (
|
||||
PrimitiveUnserializableToJSONError,
|
||||
UntypedComputedVarError,
|
||||
)
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import (
|
||||
@ -270,7 +273,7 @@ def test_get_setter(prop: Var, expected):
|
||||
([1, 2, 3], Var(_js_expr="[1, 2, 3]", _var_type=List[int])),
|
||||
(
|
||||
{"a": 1, "b": 2},
|
||||
Var(_js_expr='({ ["a"] : 1, ["b"] : 2 })', _var_type=Dict[str, int]),
|
||||
Var(_js_expr='({ ["a"] : 1, ["b"] : 2 })', _var_type=Mapping[str, int]),
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -804,7 +807,7 @@ def test_shadow_computed_var_error(request: pytest.FixtureRequest, fixture: str)
|
||||
request: Fixture Request.
|
||||
fixture: The state fixture.
|
||||
"""
|
||||
with pytest.raises(NameError):
|
||||
with pytest.raises(UntypedComputedVarError):
|
||||
state = request.getfixturevalue(fixture)
|
||||
state.var_without_annotation.foo
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, Union
|
||||
from typing import List, Mapping, Union
|
||||
|
||||
import pytest
|
||||
|
||||
@ -37,12 +37,12 @@ class ChildGenericDict(GenericDict):
|
||||
("a", str),
|
||||
([1, 2, 3], List[int]),
|
||||
([1, 2.0, "a"], List[Union[int, float, str]]),
|
||||
({"a": 1, "b": 2}, Dict[str, int]),
|
||||
({"a": 1, 2: "b"}, Dict[Union[int, str], Union[str, int]]),
|
||||
({"a": 1, "b": 2}, Mapping[str, int]),
|
||||
({"a": 1, 2: "b"}, Mapping[Union[int, str], Union[str, int]]),
|
||||
(CustomDict(), CustomDict),
|
||||
(ChildCustomDict(), ChildCustomDict),
|
||||
(GenericDict({1: 1}), Dict[int, int]),
|
||||
(ChildGenericDict({1: 1}), Dict[int, int]),
|
||||
(GenericDict({1: 1}), Mapping[int, int]),
|
||||
(ChildGenericDict({1: 1}), Mapping[int, int]),
|
||||
],
|
||||
)
|
||||
def test_figure_out_type(value, expected):
|
||||
|
Loading…
Reference in New Issue
Block a user