removing deprecated features for 0.7.0 and removing py3.9 support (#4586)
* remove deprecated features and support for py3.9 * remove other deprecated stuff * update lock file * fix units tests * relock poetry * fix _replace for computed_var * fix some merge typo * fix typing of deploy args * fix benchmarks.yml versions * console.error instead of raising Exception * fix tests * ignore lambdas when resolving annotations * simplify redirect logic in event.py * more fixes * fix unit tests again * give back default annotations for lambdas * fix signature check for on_submit * remove useless stuff * update pyi * readd the getattr * raise if log_level is wrong type * silly goose, loglevel is a subclass of str * i don't believe this code * add guard --------- Co-authored-by: Khaleel Al-Adhami <khaleel.aladhami@gmail.com>
This commit is contained in:
parent
db13fe65b8
commit
4c2b2ed1c6
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
|
||||
|
||||
|
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]
|
||||
@ -102,5 +101,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"]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
176
reflex/event.py
176
reflex/event.py
@ -25,7 +25,6 @@ from typing import (
|
||||
overload,
|
||||
)
|
||||
|
||||
import typing_extensions
|
||||
from typing_extensions import (
|
||||
Concatenate,
|
||||
ParamSpec,
|
||||
@ -40,7 +39,11 @@ from typing_extensions import (
|
||||
from reflex import constants
|
||||
from reflex.constants.state import FRONTEND_EVENT_STATE
|
||||
from reflex.utils import console, format
|
||||
from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgTypeMismatch
|
||||
from reflex.utils.exceptions import (
|
||||
EventFnArgMismatchError,
|
||||
EventHandlerArgTypeMismatchError,
|
||||
MissingAnnotationError,
|
||||
)
|
||||
from reflex.utils.types import ArgsSpec, GenericType, typehint_issubclass
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import LiteralVar, Var
|
||||
@ -96,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,
|
||||
@ -813,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.
|
||||
|
||||
@ -843,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),
|
||||
@ -1279,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
|
||||
|
||||
@ -1320,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(
|
||||
@ -1335,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 = EventHandlerArgTypeMismatch(
|
||||
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])
|
||||
|
||||
@ -1393,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
|
||||
|
||||
@ -1420,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
|
||||
|
||||
|
||||
@ -1461,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
|
||||
]
|
||||
@ -1477,7 +1420,7 @@ def check_fn_match_arg_spec(
|
||||
func_name: str | None = None,
|
||||
):
|
||||
"""Ensures that the function signature matches the passed argument specification
|
||||
or raises an EventFnArgMismatch if they do not.
|
||||
or raises an EventFnArgMismatchError if they do not.
|
||||
|
||||
Args:
|
||||
user_func: The function to be validated.
|
||||
@ -1487,7 +1430,7 @@ def check_fn_match_arg_spec(
|
||||
func_name: The name of the function to be validated.
|
||||
|
||||
Raises:
|
||||
EventFnArgMismatch: Raised if the number of mandatory arguments do not match
|
||||
EventFnArgMismatchError: Raised if the number of mandatory arguments do not match
|
||||
"""
|
||||
user_args = inspect.getfullargspec(user_func).args
|
||||
# Drop the first argument if it's a bound method
|
||||
@ -1503,7 +1446,7 @@ def check_fn_match_arg_spec(
|
||||
number_of_event_args = len(parsed_event_args)
|
||||
|
||||
if number_of_user_args - number_of_user_default_args > number_of_event_args:
|
||||
raise EventFnArgMismatch(
|
||||
raise EventFnArgMismatchError(
|
||||
f"Event {key} only provides {number_of_event_args} arguments, but "
|
||||
f"{func_name or user_func} requires at least {number_of_user_args - number_of_user_default_args} "
|
||||
"arguments to be passed to the event handler.\n"
|
||||
@ -1812,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."""
|
||||
@ -1986,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.
|
||||
"""
|
||||
@ -1994,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
|
||||
)
|
@ -505,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
|
||||
|
||||
@ -516,6 +517,21 @@ def deploy(
|
||||
# Set the log level.
|
||||
console.set_log_level(loglevel)
|
||||
|
||||
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.
|
||||
if interactive:
|
||||
@ -525,9 +541,7 @@ 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,
|
||||
@ -551,12 +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,
|
||||
config_path=config_path,
|
||||
project_name=project_name,
|
||||
**extra,
|
||||
**({"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)
|
||||
|
||||
|
||||
|
@ -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."""
|
||||
|
||||
@ -111,11 +135,11 @@ class MatchTypeError(ReflexError, TypeError):
|
||||
"""Raised when the return types of match cases are different."""
|
||||
|
||||
|
||||
class EventHandlerArgTypeMismatch(ReflexError, TypeError):
|
||||
class EventHandlerArgTypeMismatchError(ReflexError, TypeError):
|
||||
"""Raised when the annotations of args accepted by an EventHandler differs from the spec of the event trigger."""
|
||||
|
||||
|
||||
class EventFnArgMismatch(ReflexError, TypeError):
|
||||
class EventFnArgMismatchError(ReflexError, TypeError):
|
||||
"""Raised when the number of args required by an event handler is more than provided by the event trigger."""
|
||||
|
||||
|
||||
@ -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())),
|
||||
)
|
||||
|
||||
|
||||
@ -526,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()
|
||||
|
@ -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,
|
||||
|
@ -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 (
|
||||
GeneratedCodeHasNoFunctionDefs,
|
||||
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
|
||||
|
||||
@ -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(
|
||||
|
@ -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
|
||||
|
@ -46,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,
|
||||
@ -545,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
|
||||
@ -1863,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)
|
||||
|
||||
@ -1948,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:
|
||||
@ -2082,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
|
||||
|
@ -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,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, EventFnArgMismatch
|
||||
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):
|
||||
@ -908,30 +916,29 @@ def test_invalid_event_handler_args(component2, test_state):
|
||||
test_state: A test state.
|
||||
"""
|
||||
# EventHandler args must match
|
||||
with pytest.raises(EventFnArgMismatch):
|
||||
with pytest.raises(EventFnArgMismatchError):
|
||||
component2.create(on_click=test_state.do_something_arg)
|
||||
|
||||
# Multiple EventHandler args: all must match
|
||||
with pytest.raises(EventFnArgMismatch):
|
||||
with pytest.raises(EventFnArgMismatchError):
|
||||
component2.create(
|
||||
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):
|
||||
@ -944,15 +951,15 @@ def test_invalid_event_handler_args(component2, test_state):
|
||||
)
|
||||
|
||||
# lambda signature must match event trigger.
|
||||
with pytest.raises(EventFnArgMismatch):
|
||||
with pytest.raises(EventFnArgMismatchError):
|
||||
component2.create(on_click=lambda _: test_state.do_something_arg(1))
|
||||
|
||||
# lambda returning EventHandler must match spec
|
||||
with pytest.raises(EventFnArgMismatch):
|
||||
with pytest.raises(EventFnArgMismatchError):
|
||||
component2.create(on_click=lambda: test_state.do_something_arg)
|
||||
|
||||
# Mixed EventSpec and EventHandler must match spec.
|
||||
with pytest.raises(EventFnArgMismatch):
|
||||
with pytest.raises(EventFnArgMismatchError):
|
||||
component2.create(
|
||||
on_click=lambda: [
|
||||
test_state.do_something_arg(1),
|
||||
@ -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):
|
||||
|
@ -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 PrimitiveUnserializableToJSON
|
||||
from reflex.utils.exceptions import (
|
||||
PrimitiveUnserializableToJSON,
|
||||
UntypedComputedVarError,
|
||||
)
|
||||
from reflex.utils.imports import ImportVar
|
||||
from reflex.vars import VarData
|
||||
from reflex.vars.base import (
|
||||
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user