Merge branch 'main' into remove-some-benchmarks-from-CI
This commit is contained in:
commit
29f3a77a48
@ -3,7 +3,7 @@ fail_fast: true
|
|||||||
repos:
|
repos:
|
||||||
|
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
rev: v0.9.3
|
rev: v0.9.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
args: [reflex, tests]
|
args: [reflex, tests]
|
||||||
|
40
poetry.lock
generated
40
poetry.lock
generated
@ -2415,31 +2415,31 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.9.3"
|
version = "0.9.6"
|
||||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
groups = ["dev"]
|
groups = ["dev"]
|
||||||
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624"},
|
{file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"},
|
||||||
{file = "ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c"},
|
{file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"},
|
||||||
{file = "ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4"},
|
{file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"},
|
||||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439"},
|
{file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"},
|
||||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5"},
|
{file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"},
|
||||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4"},
|
{file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"},
|
||||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1"},
|
{file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"},
|
||||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5"},
|
{file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"},
|
||||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4"},
|
{file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"},
|
||||||
{file = "ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6"},
|
{file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"},
|
||||||
{file = "ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730"},
|
{file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"},
|
||||||
{file = "ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2"},
|
{file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"},
|
||||||
{file = "ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519"},
|
{file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"},
|
||||||
{file = "ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b"},
|
{file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"},
|
||||||
{file = "ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c"},
|
{file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"},
|
||||||
{file = "ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4"},
|
{file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"},
|
||||||
{file = "ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b"},
|
{file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"},
|
||||||
{file = "ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a"},
|
{file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3188,4 +3188,4 @@ type = ["pytest-mypy"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.10, <4.0"
|
python-versions = ">=3.10, <4.0"
|
||||||
content-hash = "3b7e6e6e872c68f951f191d85a7d76fe1dd86caf32e2143a53a3152a3686fc7f"
|
content-hash = "7ae644e1c5b910f4fd0d8ab0b530818077a96e5d329b2be1269e967c6b0b3d25"
|
||||||
|
@ -61,7 +61,7 @@ dill = ">=0.3.8"
|
|||||||
toml = ">=0.10.2,<1.0"
|
toml = ">=0.10.2,<1.0"
|
||||||
pytest-asyncio = ">=0.24.0"
|
pytest-asyncio = ">=0.24.0"
|
||||||
pytest-cov = ">=4.0.0,<7.0"
|
pytest-cov = ">=4.0.0,<7.0"
|
||||||
ruff = "0.9.3"
|
ruff = "0.9.6"
|
||||||
pandas = ">=2.1.1,<3.0"
|
pandas = ">=2.1.1,<3.0"
|
||||||
pillow = ">=10.0.0,<12.0"
|
pillow = ">=10.0.0,<12.0"
|
||||||
plotly = ">=5.13.0,<6.0"
|
plotly = ">=5.13.0,<6.0"
|
||||||
|
@ -22,6 +22,7 @@ from typing import (
|
|||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
AsyncIterator,
|
AsyncIterator,
|
||||||
|
BinaryIO,
|
||||||
Callable,
|
Callable,
|
||||||
Coroutine,
|
Coroutine,
|
||||||
Dict,
|
Dict,
|
||||||
@ -35,12 +36,15 @@ from typing import (
|
|||||||
get_type_hints,
|
get_type_hints,
|
||||||
)
|
)
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Request, UploadFile
|
from fastapi import FastAPI, HTTPException, Request
|
||||||
|
from fastapi import UploadFile as FastAPIUploadFile
|
||||||
from fastapi.middleware import cors
|
from fastapi.middleware import cors
|
||||||
from fastapi.responses import JSONResponse, StreamingResponse
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
||||||
from socketio import ASGIApp, AsyncNamespace, AsyncServer
|
from socketio import ASGIApp, AsyncNamespace, AsyncServer
|
||||||
|
from starlette.datastructures import Headers
|
||||||
|
from starlette.datastructures import UploadFile as StarletteUploadFile
|
||||||
from starlette_admin.contrib.sqla.admin import Admin
|
from starlette_admin.contrib.sqla.admin import Admin
|
||||||
from starlette_admin.contrib.sqla.view import ModelView
|
from starlette_admin.contrib.sqla.view import ModelView
|
||||||
|
|
||||||
@ -231,6 +235,53 @@ class OverlayFragment(Fragment):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class UploadFile(StarletteUploadFile):
|
||||||
|
"""A file uploaded to the server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file: The standard Python file object (non-async).
|
||||||
|
filename: The original file name.
|
||||||
|
size: The size of the file in bytes.
|
||||||
|
headers: The headers of the request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
file: BinaryIO
|
||||||
|
|
||||||
|
path: Optional[Path] = dataclasses.field(default=None)
|
||||||
|
|
||||||
|
_deprecated_filename: Optional[str] = dataclasses.field(default=None)
|
||||||
|
|
||||||
|
size: Optional[int] = dataclasses.field(default=None)
|
||||||
|
|
||||||
|
headers: Headers = dataclasses.field(default_factory=Headers)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> Optional[str]:
|
||||||
|
"""Get the name of the uploaded file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The name of the uploaded file.
|
||||||
|
"""
|
||||||
|
if self.path:
|
||||||
|
return self.path.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filename(self) -> Optional[str]:
|
||||||
|
"""Get the filename of the uploaded file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The filename of the uploaded file.
|
||||||
|
"""
|
||||||
|
console.deprecate(
|
||||||
|
feature_name="UploadFile.filename",
|
||||||
|
reason="Use UploadFile.name instead.",
|
||||||
|
deprecation_version="0.7.1",
|
||||||
|
removal_version="0.8.0",
|
||||||
|
)
|
||||||
|
return self._deprecated_filename
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(
|
@dataclasses.dataclass(
|
||||||
frozen=True,
|
frozen=True,
|
||||||
)
|
)
|
||||||
@ -591,7 +642,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Returns:
|
Returns:
|
||||||
The generated component.
|
The generated component.
|
||||||
"""
|
"""
|
||||||
return component if isinstance(component, Component) else component()
|
from reflex.compiler.compiler import into_component
|
||||||
|
|
||||||
|
return into_component(component)
|
||||||
|
|
||||||
def add_page(
|
def add_page(
|
||||||
self,
|
self,
|
||||||
@ -1583,7 +1636,7 @@ def upload(app: App):
|
|||||||
The upload function.
|
The upload function.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def upload_file(request: Request, files: List[UploadFile]):
|
async def upload_file(request: Request, files: List[FastAPIUploadFile]):
|
||||||
"""Upload a file.
|
"""Upload a file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1659,7 +1712,8 @@ def upload(app: App):
|
|||||||
file_copies.append(
|
file_copies.append(
|
||||||
UploadFile(
|
UploadFile(
|
||||||
file=content_copy,
|
file=content_copy,
|
||||||
filename=file.filename,
|
path=Path(file.filename.lstrip("/")) if file.filename else None,
|
||||||
|
_deprecated_filename=file.filename,
|
||||||
size=file.size,
|
size=file.size,
|
||||||
headers=file.headers,
|
headers=file.headers,
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Tuple, Type, Union
|
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Sequence, Tuple, Type, Union
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.compiler import templates, utils
|
from reflex.compiler import templates, utils
|
||||||
@ -545,7 +545,47 @@ def purge_web_pages_dir():
|
|||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from reflex.app import UnevaluatedPage
|
from reflex.app import ComponentCallable, UnevaluatedPage
|
||||||
|
|
||||||
|
|
||||||
|
def _into_component_once(component: Component | ComponentCallable) -> Component | None:
|
||||||
|
"""Convert a component to a Component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component: The component to convert.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The converted component.
|
||||||
|
"""
|
||||||
|
if isinstance(component, Component):
|
||||||
|
return component
|
||||||
|
if isinstance(component, (Var, int, float, str)):
|
||||||
|
return Fragment.create(component)
|
||||||
|
if isinstance(component, Sequence):
|
||||||
|
return Fragment.create(*component)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def into_component(component: Component | ComponentCallable) -> Component:
|
||||||
|
"""Convert a component to a Component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component: The component to convert.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The converted component.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: If the component is not a Component.
|
||||||
|
"""
|
||||||
|
if (converted := _into_component_once(component)) is not None:
|
||||||
|
return converted
|
||||||
|
if (
|
||||||
|
callable(component)
|
||||||
|
and (converted := _into_component_once(component())) is not None
|
||||||
|
):
|
||||||
|
return converted
|
||||||
|
raise TypeError(f"Expected a Component, got {type(component)}")
|
||||||
|
|
||||||
|
|
||||||
def compile_unevaluated_page(
|
def compile_unevaluated_page(
|
||||||
@ -568,12 +608,7 @@ def compile_unevaluated_page(
|
|||||||
The compiled component and whether state should be enabled.
|
The compiled component and whether state should be enabled.
|
||||||
"""
|
"""
|
||||||
# Generate the component if it is a callable.
|
# Generate the component if it is a callable.
|
||||||
component = page.component
|
component = into_component(page.component)
|
||||||
component = component if isinstance(component, Component) else component()
|
|
||||||
|
|
||||||
# unpack components that return tuples in an rx.fragment.
|
|
||||||
if isinstance(component, tuple):
|
|
||||||
component = Fragment.create(*component)
|
|
||||||
|
|
||||||
component._add_style_recursive(style or {}, theme)
|
component._add_style_recursive(style or {}, theme)
|
||||||
|
|
||||||
@ -678,10 +713,8 @@ class ExecutorSafeFunctions:
|
|||||||
The route, compiled component, and compiled page.
|
The route, compiled component, and compiled page.
|
||||||
"""
|
"""
|
||||||
component, enable_state = compile_unevaluated_page(
|
component, enable_state = compile_unevaluated_page(
|
||||||
route, cls.UNCOMPILED_PAGES[route]
|
route, cls.UNCOMPILED_PAGES[route], cls.STATE, style, theme
|
||||||
)
|
)
|
||||||
component = component if isinstance(component, Component) else component()
|
|
||||||
component._add_style_recursive(style, theme)
|
|
||||||
return route, component, compile_page(route, component, cls.STATE)
|
return route, component, compile_page(route, component, cls.STATE)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -51,13 +51,7 @@ from reflex.event import (
|
|||||||
)
|
)
|
||||||
from reflex.style import Style, format_as_emotion
|
from reflex.style import Style, format_as_emotion
|
||||||
from reflex.utils import format, imports, types
|
from reflex.utils import format, imports, types
|
||||||
from reflex.utils.imports import (
|
from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
|
||||||
ImmutableParsedImportDict,
|
|
||||||
ImportDict,
|
|
||||||
ImportVar,
|
|
||||||
ParsedImportDict,
|
|
||||||
parse_imports,
|
|
||||||
)
|
|
||||||
from reflex.vars import VarData
|
from reflex.vars import VarData
|
||||||
from reflex.vars.base import (
|
from reflex.vars.base import (
|
||||||
CachedVarOperation,
|
CachedVarOperation,
|
||||||
@ -1208,7 +1202,7 @@ class Component(BaseComponent, ABC):
|
|||||||
Returns:
|
Returns:
|
||||||
True if the dependency should be transpiled.
|
True if the dependency should be transpiled.
|
||||||
"""
|
"""
|
||||||
return (
|
return bool(self.transpile_packages) and (
|
||||||
dep in self.transpile_packages
|
dep in self.transpile_packages
|
||||||
or format.format_library_name(dep or "") in self.transpile_packages
|
or format.format_library_name(dep or "") in self.transpile_packages
|
||||||
)
|
)
|
||||||
@ -1291,9 +1285,10 @@ class Component(BaseComponent, ABC):
|
|||||||
event_imports = Imports.EVENTS if self.event_triggers else {}
|
event_imports = Imports.EVENTS if self.event_triggers else {}
|
||||||
|
|
||||||
# Collect imports from Vars used directly by this component.
|
# Collect imports from Vars used directly by this component.
|
||||||
var_datas = [var._get_all_var_data() for var in self._get_vars()]
|
var_imports = [
|
||||||
var_imports: List[ImmutableParsedImportDict] = [
|
var_data.imports
|
||||||
var_data.imports for var_data in var_datas if var_data is not None
|
for var in self._get_vars()
|
||||||
|
if (var_data := var._get_all_var_data()) is not None
|
||||||
]
|
]
|
||||||
|
|
||||||
added_import_dicts: list[ParsedImportDict] = []
|
added_import_dicts: list[ParsedImportDict] = []
|
||||||
|
@ -29,7 +29,7 @@ from reflex.event import (
|
|||||||
from reflex.utils import format
|
from reflex.utils import format
|
||||||
from reflex.utils.imports import ImportVar
|
from reflex.utils.imports import ImportVar
|
||||||
from reflex.vars import VarData
|
from reflex.vars import VarData
|
||||||
from reflex.vars.base import CallableVar, Var, get_unique_variable_name
|
from reflex.vars.base import Var, get_unique_variable_name
|
||||||
from reflex.vars.sequence import LiteralStringVar
|
from reflex.vars.sequence import LiteralStringVar
|
||||||
|
|
||||||
DEFAULT_UPLOAD_ID: str = "default"
|
DEFAULT_UPLOAD_ID: str = "default"
|
||||||
@ -45,7 +45,6 @@ upload_files_context_var_data: VarData = VarData(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@CallableVar
|
|
||||||
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var:
|
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var:
|
||||||
"""Get the file upload drop trigger.
|
"""Get the file upload drop trigger.
|
||||||
|
|
||||||
@ -75,7 +74,6 @@ def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@CallableVar
|
|
||||||
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var:
|
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var:
|
||||||
"""Get the list of selected files.
|
"""Get the list of selected files.
|
||||||
|
|
||||||
|
@ -13,14 +13,12 @@ from reflex.event import CallableEventSpec, EventSpec, EventType
|
|||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
from reflex.utils.imports import ImportVar
|
from reflex.utils.imports import ImportVar
|
||||||
from reflex.vars import VarData
|
from reflex.vars import VarData
|
||||||
from reflex.vars.base import CallableVar, Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
DEFAULT_UPLOAD_ID: str
|
DEFAULT_UPLOAD_ID: str
|
||||||
upload_files_context_var_data: VarData
|
upload_files_context_var_data: VarData
|
||||||
|
|
||||||
@CallableVar
|
|
||||||
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
|
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
|
||||||
@CallableVar
|
|
||||||
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
|
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> Var: ...
|
||||||
@CallableEventSpec
|
@CallableEventSpec
|
||||||
def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ...
|
def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ...
|
||||||
|
@ -144,7 +144,7 @@ class ColorModeIconButton(IconButton):
|
|||||||
|
|
||||||
if allow_system:
|
if allow_system:
|
||||||
|
|
||||||
def color_mode_item(_color_mode: str):
|
def color_mode_item(_color_mode: Literal["light", "dark", "system"]):
|
||||||
return dropdown_menu.item(
|
return dropdown_menu.item(
|
||||||
_color_mode.title(), on_click=set_color_mode(_color_mode)
|
_color_mode.title(), on_click=set_color_mode(_color_mode)
|
||||||
)
|
)
|
||||||
|
@ -10,6 +10,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
from functools import lru_cache
|
||||||
from importlib.util import find_spec
|
from importlib.util import find_spec
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
@ -408,6 +409,19 @@ class EnvVar(Generic[T]):
|
|||||||
os.environ[self.name] = str_value
|
os.environ[self.name] = str_value
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def get_type_hints_environment(cls: type) -> dict[str, Any]:
|
||||||
|
"""Get the type hints for the environment variables.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cls: The class.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The type hints.
|
||||||
|
"""
|
||||||
|
return get_type_hints(cls)
|
||||||
|
|
||||||
|
|
||||||
class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
|
class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
|
||||||
"""Descriptor for environment variables."""
|
"""Descriptor for environment variables."""
|
||||||
|
|
||||||
@ -434,7 +448,9 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
|
|||||||
"""
|
"""
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def __get__(self, instance: Any, owner: Any):
|
def __get__(
|
||||||
|
self, instance: EnvironmentVariables, owner: type[EnvironmentVariables]
|
||||||
|
):
|
||||||
"""Get the EnvVar instance.
|
"""Get the EnvVar instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -444,7 +460,7 @@ class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
|
|||||||
Returns:
|
Returns:
|
||||||
The EnvVar instance.
|
The EnvVar instance.
|
||||||
"""
|
"""
|
||||||
type_ = get_args(get_type_hints(owner)[self.name])[0]
|
type_ = get_args(get_type_hints_environment(owner)[self.name])[0]
|
||||||
env_name = self.name
|
env_name = self.name
|
||||||
if self.internal:
|
if self.internal:
|
||||||
env_name = f"__{env_name}"
|
env_name = f"__{env_name}"
|
||||||
|
@ -1742,6 +1742,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|||||||
|
|
||||||
Yields:
|
Yields:
|
||||||
StateUpdate object
|
StateUpdate object
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If a string value is received for an int or float type and cannot be converted.
|
||||||
"""
|
"""
|
||||||
from reflex.utils import telemetry
|
from reflex.utils import telemetry
|
||||||
|
|
||||||
@ -1779,12 +1782,25 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|||||||
hinted_args, (Base, BaseModelV1, BaseModelV2)
|
hinted_args, (Base, BaseModelV1, BaseModelV2)
|
||||||
):
|
):
|
||||||
payload[arg] = hinted_args(**value)
|
payload[arg] = hinted_args(**value)
|
||||||
if isinstance(value, list) and (hinted_args is set or hinted_args is Set):
|
elif isinstance(value, list) and (hinted_args is set or hinted_args is Set):
|
||||||
payload[arg] = set(value)
|
payload[arg] = set(value)
|
||||||
if isinstance(value, list) and (
|
elif isinstance(value, list) and (
|
||||||
hinted_args is tuple or hinted_args is Tuple
|
hinted_args is tuple or hinted_args is Tuple
|
||||||
):
|
):
|
||||||
payload[arg] = tuple(value)
|
payload[arg] = tuple(value)
|
||||||
|
elif isinstance(value, str) and (
|
||||||
|
hinted_args is int or hinted_args is float
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
payload[arg] = hinted_args(value)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
f"Received a string value ({value}) for {arg} but expected a {hinted_args}"
|
||||||
|
) from None
|
||||||
|
else:
|
||||||
|
console.warn(
|
||||||
|
f"Received a string value ({value}) for {arg} but expected a {hinted_args}. A simple conversion was successful."
|
||||||
|
)
|
||||||
|
|
||||||
# Wrap the function in a try/except block.
|
# Wrap the function in a try/except block.
|
||||||
try:
|
try:
|
||||||
@ -2459,6 +2475,8 @@ class ComponentState(State, mixin=True):
|
|||||||
Returns:
|
Returns:
|
||||||
A new instance of the Component with an independent copy of the State.
|
A new instance of the Component with an independent copy of the State.
|
||||||
"""
|
"""
|
||||||
|
from reflex.compiler.compiler import into_component
|
||||||
|
|
||||||
cls._per_component_state_instance_count += 1
|
cls._per_component_state_instance_count += 1
|
||||||
state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
|
state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
|
||||||
component_state = type(
|
component_state = type(
|
||||||
@ -2470,6 +2488,7 @@ class ComponentState(State, mixin=True):
|
|||||||
# Save a reference to the dynamic state for pickle/unpickle.
|
# Save a reference to the dynamic state for pickle/unpickle.
|
||||||
setattr(reflex.istate.dynamic, state_cls_name, component_state)
|
setattr(reflex.istate.dynamic, state_cls_name, component_state)
|
||||||
component = component_state.get_component(*children, **props)
|
component = component_state.get_component(*children, **props)
|
||||||
|
component = into_component(component)
|
||||||
component.State = component_state
|
component.State = component_state
|
||||||
return component
|
return component
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from reflex.utils.exceptions import ReflexError
|
|||||||
from reflex.utils.imports import ImportVar
|
from reflex.utils.imports import ImportVar
|
||||||
from reflex.utils.types import get_origin
|
from reflex.utils.types import get_origin
|
||||||
from reflex.vars import VarData
|
from reflex.vars import VarData
|
||||||
from reflex.vars.base import CallableVar, LiteralVar, Var
|
from reflex.vars.base import LiteralVar, Var
|
||||||
from reflex.vars.function import FunctionVar
|
from reflex.vars.function import FunctionVar
|
||||||
from reflex.vars.object import ObjectVar
|
from reflex.vars.object import ObjectVar
|
||||||
|
|
||||||
@ -48,7 +48,6 @@ def _color_mode_var(_js_expr: str, _var_type: Type = str) -> Var:
|
|||||||
).guess_type()
|
).guess_type()
|
||||||
|
|
||||||
|
|
||||||
@CallableVar
|
|
||||||
def set_color_mode(
|
def set_color_mode(
|
||||||
new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None,
|
new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None,
|
||||||
) -> Var[EventChain]:
|
) -> Var[EventChain]:
|
||||||
|
@ -951,7 +951,7 @@ class Var(Generic[VAR_TYPE]):
|
|||||||
"""
|
"""
|
||||||
actual_name = self._var_field_name
|
actual_name = self._var_field_name
|
||||||
|
|
||||||
def setter(state: BaseState, value: Any):
|
def setter(state: Any, value: Any):
|
||||||
"""Get the setter for the var.
|
"""Get the setter for the var.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -969,6 +969,8 @@ class Var(Generic[VAR_TYPE]):
|
|||||||
else:
|
else:
|
||||||
setattr(state, actual_name, value)
|
setattr(state, actual_name, value)
|
||||||
|
|
||||||
|
setter.__annotations__["value"] = self._var_type
|
||||||
|
|
||||||
setter.__qualname__ = self._get_setter_name()
|
setter.__qualname__ = self._get_setter_name()
|
||||||
|
|
||||||
return setter
|
return setter
|
||||||
@ -1901,61 +1903,6 @@ def _or_operation(a: Var, b: Var):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(
|
|
||||||
eq=False,
|
|
||||||
frozen=True,
|
|
||||||
slots=True,
|
|
||||||
)
|
|
||||||
class CallableVar(Var):
|
|
||||||
"""Decorate a Var-returning function to act as both a Var and a function.
|
|
||||||
|
|
||||||
This is used as a compatibility shim for replacing Var objects in the
|
|
||||||
API with functions that return a family of Var.
|
|
||||||
"""
|
|
||||||
|
|
||||||
fn: Callable[..., Var] = dataclasses.field(
|
|
||||||
default_factory=lambda: lambda: Var(_js_expr="undefined")
|
|
||||||
)
|
|
||||||
original_var: Var = dataclasses.field(
|
|
||||||
default_factory=lambda: Var(_js_expr="undefined")
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, fn: Callable[..., Var]):
|
|
||||||
"""Initialize a CallableVar.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fn: The function to decorate (must return Var)
|
|
||||||
"""
|
|
||||||
original_var = fn()
|
|
||||||
super(CallableVar, self).__init__(
|
|
||||||
_js_expr=original_var._js_expr,
|
|
||||||
_var_type=original_var._var_type,
|
|
||||||
_var_data=VarData.merge(original_var._get_all_var_data()),
|
|
||||||
)
|
|
||||||
object.__setattr__(self, "fn", fn)
|
|
||||||
object.__setattr__(self, "original_var", original_var)
|
|
||||||
|
|
||||||
def __call__(self, *args: Any, **kwargs: Any) -> Var:
|
|
||||||
"""Call the decorated function.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
*args: The args to pass to the function.
|
|
||||||
**kwargs: The kwargs to pass to the function.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The Var returned from calling the function.
|
|
||||||
"""
|
|
||||||
return self.fn(*args, **kwargs)
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
"""Calculate the hash of the object.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The hash of the object.
|
|
||||||
"""
|
|
||||||
return hash((type(self).__name__, self.original_var))
|
|
||||||
|
|
||||||
|
|
||||||
RETURN_TYPE = TypeVar("RETURN_TYPE")
|
RETURN_TYPE = TypeVar("RETURN_TYPE")
|
||||||
|
|
||||||
DICT_KEY = TypeVar("DICT_KEY")
|
DICT_KEY = TypeVar("DICT_KEY")
|
||||||
|
@ -20,7 +20,11 @@ def BackgroundTask():
|
|||||||
class State(rx.State):
|
class State(rx.State):
|
||||||
counter: int = 0
|
counter: int = 0
|
||||||
_task_id: int = 0
|
_task_id: int = 0
|
||||||
iterations: int = 10
|
iterations: rx.Field[int] = rx.field(10)
|
||||||
|
|
||||||
|
@rx.event
|
||||||
|
def set_iterations(self, value: str):
|
||||||
|
self.iterations = int(value)
|
||||||
|
|
||||||
@rx.event(background=True)
|
@rx.event(background=True)
|
||||||
async def handle_event(self):
|
async def handle_event(self):
|
||||||
@ -125,8 +129,8 @@ def BackgroundTask():
|
|||||||
rx.input(
|
rx.input(
|
||||||
id="iterations",
|
id="iterations",
|
||||||
placeholder="Iterations",
|
placeholder="Iterations",
|
||||||
value=State.iterations.to_string(), # pyright: ignore [reportAttributeAccessIssue]
|
value=State.iterations.to_string(),
|
||||||
on_change=State.set_iterations, # pyright: ignore [reportAttributeAccessIssue]
|
on_change=State.set_iterations,
|
||||||
),
|
),
|
||||||
rx.button(
|
rx.button(
|
||||||
"Delayed Increment",
|
"Delayed Increment",
|
||||||
|
@ -87,7 +87,7 @@ def UploadFile():
|
|||||||
),
|
),
|
||||||
rx.box(
|
rx.box(
|
||||||
rx.foreach(
|
rx.foreach(
|
||||||
rx.selected_files,
|
rx.selected_files(),
|
||||||
lambda f: rx.text(f, as_="p"),
|
lambda f: rx.text(f, as_="p"),
|
||||||
),
|
),
|
||||||
id="selected_files",
|
id="selected_files",
|
||||||
|
@ -61,7 +61,7 @@ def ColorToggleApp():
|
|||||||
rx.icon(tag="moon", size=20),
|
rx.icon(tag="moon", size=20),
|
||||||
value="dark",
|
value="dark",
|
||||||
),
|
),
|
||||||
on_change=set_color_mode,
|
on_change=set_color_mode(),
|
||||||
variant="classic",
|
variant="classic",
|
||||||
radius="large",
|
radius="large",
|
||||||
value=color_mode,
|
value=color_mode,
|
||||||
|
Loading…
Reference in New Issue
Block a user