reflex 0.8.15a0__py3-none-any.whl → 0.8.15a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of reflex might be problematic. Click here for more details.
- reflex/.templates/web/utils/state.js +68 -8
- reflex/__init__.py +11 -6
- reflex/__init__.pyi +9 -2
- reflex/app.py +5 -5
- reflex/base.py +8 -11
- reflex/components/field.py +3 -1
- reflex/components/markdown/markdown.py +101 -27
- reflex/constants/base.py +5 -0
- reflex/constants/installer.py +2 -2
- reflex/event.py +3 -0
- reflex/istate/manager/__init__.py +120 -0
- reflex/istate/manager/disk.py +210 -0
- reflex/istate/manager/memory.py +76 -0
- reflex/istate/{manager.py → manager/redis.py} +5 -372
- reflex/model.py +5 -1
- reflex/state.py +14 -9
- reflex/testing.py +4 -8
- reflex/utils/codespaces.py +30 -1
- reflex/utils/compat.py +49 -1
- reflex/utils/misc.py +2 -1
- reflex/utils/monitoring.py +1 -2
- reflex/utils/prerequisites.py +17 -3
- reflex/utils/processes.py +3 -1
- reflex/utils/redir.py +21 -37
- reflex/utils/templates.py +4 -4
- reflex/utils/types.py +9 -1
- reflex/vars/base.py +106 -25
- reflex/vars/color.py +28 -8
- reflex/vars/datetime.py +6 -2
- reflex/vars/dep_tracking.py +2 -2
- reflex/vars/number.py +26 -0
- reflex/vars/object.py +23 -6
- reflex/vars/sequence.py +32 -1
- {reflex-0.8.15a0.dist-info → reflex-0.8.15a2.dist-info}/METADATA +4 -3
- {reflex-0.8.15a0.dist-info → reflex-0.8.15a2.dist-info}/RECORD +38 -35
- {reflex-0.8.15a0.dist-info → reflex-0.8.15a2.dist-info}/WHEEL +0 -0
- {reflex-0.8.15a0.dist-info → reflex-0.8.15a2.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.15a0.dist-info → reflex-0.8.15a2.dist-info}/licenses/LICENSE +0 -0
reflex/model.py
CHANGED
|
@@ -71,6 +71,10 @@ if find_spec("sqlalchemy"):
|
|
|
71
71
|
"echo": environment.SQLALCHEMY_ECHO.get(),
|
|
72
72
|
# Check connections before returning them.
|
|
73
73
|
"pool_pre_ping": environment.SQLALCHEMY_POOL_PRE_PING.get(),
|
|
74
|
+
"pool_size": environment.SQLALCHEMY_POOL_SIZE.get(),
|
|
75
|
+
"max_overflow": environment.SQLALCHEMY_MAX_OVERFLOW.get(),
|
|
76
|
+
"pool_recycle": environment.SQLALCHEMY_POOL_RECYCLE.get(),
|
|
77
|
+
"pool_timeout": environment.SQLALCHEMY_POOL_TIMEOUT.get(),
|
|
74
78
|
}
|
|
75
79
|
conf = get_config()
|
|
76
80
|
url = url or conf.db_url
|
|
@@ -363,7 +367,7 @@ if find_spec("sqlmodel") and find_spec("sqlalchemy") and find_spec("pydantic"):
|
|
|
363
367
|
reason=(
|
|
364
368
|
"Register sqlmodel.SQLModel classes with `@rx.ModelRegistry.register`"
|
|
365
369
|
),
|
|
366
|
-
deprecation_version="0.8.
|
|
370
|
+
deprecation_version="0.8.15",
|
|
367
371
|
removal_version="0.9.0",
|
|
368
372
|
)
|
|
369
373
|
super().__pydantic_init_subclass__()
|
reflex/state.py
CHANGED
|
@@ -16,6 +16,7 @@ import time
|
|
|
16
16
|
import typing
|
|
17
17
|
import warnings
|
|
18
18
|
from collections.abc import AsyncIterator, Callable, Sequence
|
|
19
|
+
from enum import Enum
|
|
19
20
|
from hashlib import md5
|
|
20
21
|
from importlib.util import find_spec
|
|
21
22
|
from types import FunctionType
|
|
@@ -246,7 +247,7 @@ class EventHandlerSetVar(EventHandler):
|
|
|
246
247
|
msg = f"Variable `{args[0]}` cannot be set on `{self.state_cls.get_full_name()}`"
|
|
247
248
|
raise AttributeError(msg)
|
|
248
249
|
|
|
249
|
-
if
|
|
250
|
+
if inspect.iscoroutinefunction(handler.fn):
|
|
250
251
|
msg = f"Setter for {args[0]} is async, which is not supported."
|
|
251
252
|
raise NotImplementedError(msg)
|
|
252
253
|
|
|
@@ -287,7 +288,7 @@ async def _resolve_delta(delta: Delta) -> Delta:
|
|
|
287
288
|
tasks = {}
|
|
288
289
|
for state_name, state_delta in delta.items():
|
|
289
290
|
for var_name, value in state_delta.items():
|
|
290
|
-
if
|
|
291
|
+
if inspect.iscoroutine(value):
|
|
291
292
|
tasks[state_name, var_name] = asyncio.create_task(
|
|
292
293
|
value,
|
|
293
294
|
name=f"reflex_resolve_delta|{state_name}|{var_name}|{time.time()}",
|
|
@@ -852,7 +853,7 @@ class BaseState(EvenMoreBasicBaseState):
|
|
|
852
853
|
ComputedVarShadowsBaseVarsError: When a computed var shadows a base var.
|
|
853
854
|
"""
|
|
854
855
|
for name, computed_var_ in cls._get_computed_vars():
|
|
855
|
-
if name in cls
|
|
856
|
+
if name in get_type_hints(cls):
|
|
856
857
|
msg = f"The computed var name `{computed_var_._js_expr}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
|
|
857
858
|
raise ComputedVarShadowsBaseVarsError(msg)
|
|
858
859
|
|
|
@@ -1554,6 +1555,8 @@ class BaseState(EvenMoreBasicBaseState):
|
|
|
1554
1555
|
RuntimeError: If redis is not used in this backend process.
|
|
1555
1556
|
StateMismatchError: If the state instance is not of the expected type.
|
|
1556
1557
|
"""
|
|
1558
|
+
from reflex.istate.manager.redis import StateManagerRedis
|
|
1559
|
+
|
|
1557
1560
|
# Then get the target state and all its substates.
|
|
1558
1561
|
state_manager = get_state_manager()
|
|
1559
1562
|
if not isinstance(state_manager, StateManagerRedis):
|
|
@@ -1733,7 +1736,7 @@ class BaseState(EvenMoreBasicBaseState):
|
|
|
1733
1736
|
except TypeError:
|
|
1734
1737
|
pass
|
|
1735
1738
|
|
|
1736
|
-
coroutines = [e for e in events if
|
|
1739
|
+
coroutines = [e for e in events if inspect.iscoroutine(e)]
|
|
1737
1740
|
|
|
1738
1741
|
for coroutine in coroutines:
|
|
1739
1742
|
coroutine_name = coroutine.__qualname__
|
|
@@ -1878,6 +1881,12 @@ class BaseState(EvenMoreBasicBaseState):
|
|
|
1878
1881
|
hinted_args is tuple or hinted_args is tuple
|
|
1879
1882
|
):
|
|
1880
1883
|
payload[arg] = tuple(value)
|
|
1884
|
+
elif isinstance(hinted_args, type) and issubclass(hinted_args, Enum):
|
|
1885
|
+
try:
|
|
1886
|
+
payload[arg] = hinted_args(value)
|
|
1887
|
+
except ValueError:
|
|
1888
|
+
msg = f"Received an invalid enum value ({value}) for {arg} of type {hinted_args}"
|
|
1889
|
+
raise ValueError(msg) from None
|
|
1881
1890
|
elif (
|
|
1882
1891
|
isinstance(value, str)
|
|
1883
1892
|
and (deserializer := _deserializers.get(hinted_args)) is not None
|
|
@@ -1895,7 +1904,7 @@ class BaseState(EvenMoreBasicBaseState):
|
|
|
1895
1904
|
# Wrap the function in a try/except block.
|
|
1896
1905
|
try:
|
|
1897
1906
|
# Handle async functions.
|
|
1898
|
-
if
|
|
1907
|
+
if inspect.iscoroutinefunction(fn.func):
|
|
1899
1908
|
events = await fn(**payload)
|
|
1900
1909
|
|
|
1901
1910
|
# Handle regular functions.
|
|
@@ -2738,11 +2747,7 @@ def reload_state_module(
|
|
|
2738
2747
|
state.get_class_substate.cache_clear()
|
|
2739
2748
|
|
|
2740
2749
|
|
|
2741
|
-
from reflex.istate.manager import LockExpiredError as LockExpiredError # noqa: E402
|
|
2742
2750
|
from reflex.istate.manager import StateManager as StateManager # noqa: E402
|
|
2743
|
-
from reflex.istate.manager import StateManagerDisk as StateManagerDisk # noqa: E402
|
|
2744
|
-
from reflex.istate.manager import StateManagerMemory as StateManagerMemory # noqa: E402
|
|
2745
|
-
from reflex.istate.manager import StateManagerRedis as StateManagerRedis # noqa: E402
|
|
2746
2751
|
from reflex.istate.manager import get_state_manager as get_state_manager # noqa: E402
|
|
2747
2752
|
from reflex.istate.manager import ( # noqa: E402
|
|
2748
2753
|
reset_disk_state_manager as reset_disk_state_manager,
|
reflex/testing.py
CHANGED
|
@@ -38,14 +38,10 @@ import reflex.utils.processes
|
|
|
38
38
|
from reflex.components.component import CustomComponent
|
|
39
39
|
from reflex.config import get_config
|
|
40
40
|
from reflex.environment import environment
|
|
41
|
-
from reflex.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
StateManagerMemory,
|
|
46
|
-
StateManagerRedis,
|
|
47
|
-
reload_state_module,
|
|
48
|
-
)
|
|
41
|
+
from reflex.istate.manager.disk import StateManagerDisk
|
|
42
|
+
from reflex.istate.manager.memory import StateManagerMemory
|
|
43
|
+
from reflex.istate.manager.redis import StateManagerRedis
|
|
44
|
+
from reflex.state import BaseState, StateManager, reload_state_module
|
|
49
45
|
from reflex.utils import console, js_runtimes
|
|
50
46
|
from reflex.utils.export import export
|
|
51
47
|
from reflex.utils.token_manager import TokenManager
|
reflex/utils/codespaces.py
CHANGED
|
@@ -26,11 +26,40 @@ def redirect_script() -> str:
|
|
|
26
26
|
const thisUrl = new URL(window.location.href);
|
|
27
27
|
const params = new URLSearchParams(thisUrl.search)
|
|
28
28
|
|
|
29
|
+
function sameHostnameDifferentPort(one, two) {{
|
|
30
|
+
const hostnameOne = one.hostname;
|
|
31
|
+
const hostnameTwo = two.hostname;
|
|
32
|
+
const partsOne = hostnameOne.split(".");
|
|
33
|
+
const partsTwo = hostnameTwo.split(".");
|
|
34
|
+
if (partsOne.length !== partsTwo.length) {{ return false; }}
|
|
35
|
+
for (let i = 1; i < partsOne.length; i++) {{
|
|
36
|
+
if (partsOne[i] !== partsTwo[i]) {{ return false; }}
|
|
37
|
+
}}
|
|
38
|
+
const uniqueNameOne = partsOne[0];
|
|
39
|
+
const uniqueNameTwo = partsTwo[0];
|
|
40
|
+
const uniqueNamePartsOne = uniqueNameOne.split("-");
|
|
41
|
+
const uniqueNamePartsTwo = uniqueNameTwo.split("-");
|
|
42
|
+
if (uniqueNamePartsOne.length !== uniqueNamePartsTwo.length) {{ return false; }}
|
|
43
|
+
for (let i = 0; i < uniqueNamePartsOne.length - 1; i++) {{
|
|
44
|
+
if (uniqueNamePartsOne[i] !== uniqueNamePartsTwo[i]) {{ return false; }}
|
|
45
|
+
}}
|
|
46
|
+
return true;
|
|
47
|
+
}}
|
|
48
|
+
|
|
29
49
|
function doRedirect(url) {{
|
|
30
50
|
if (!window.sessionStorage.getItem("authenticated_github_codespaces")) {{
|
|
31
51
|
const a = document.createElement("a");
|
|
32
52
|
if (params.has("redirect_to")) {{
|
|
33
|
-
|
|
53
|
+
const redirect_to = new URL(params.get("redirect_to"));
|
|
54
|
+
if (!sameHostnameDifferentPort(thisUrl, redirect_to)) {{
|
|
55
|
+
console.warn("Reflex: Not redirecting to different hostname");
|
|
56
|
+
return;
|
|
57
|
+
}}
|
|
58
|
+
if (!redirect_to.hostname.endsWith(".app.github.dev")) {{
|
|
59
|
+
console.warn("Reflex: Not redirecting to non .app.github.dev hostname");
|
|
60
|
+
return;
|
|
61
|
+
}}
|
|
62
|
+
a.href = redirect_to.href;
|
|
34
63
|
}} else if (!window.location.href.startsWith(url)) {{
|
|
35
64
|
a.href = url + `?redirect_to=${{window.location.href}}`
|
|
36
65
|
}} else {{
|
reflex/utils/compat.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""Compatibility hacks and helpers."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import sys
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from importlib.util import find_spec
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
4
7
|
|
|
5
8
|
if TYPE_CHECKING:
|
|
6
9
|
from pydantic.fields import FieldInfo
|
|
@@ -30,6 +33,51 @@ async def windows_hot_reload_lifespan_hack():
|
|
|
30
33
|
pass
|
|
31
34
|
|
|
32
35
|
|
|
36
|
+
def annotations_from_namespace(namespace: Mapping[str, Any]) -> dict[str, Any]:
|
|
37
|
+
"""Get the annotations from a class namespace.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
namespace: The class namespace.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
The (forward-ref) annotations from the class namespace.
|
|
44
|
+
"""
|
|
45
|
+
if sys.version_info >= (3, 14) and "__annotations__" not in namespace:
|
|
46
|
+
from annotationlib import (
|
|
47
|
+
Format,
|
|
48
|
+
call_annotate_function,
|
|
49
|
+
get_annotate_from_class_namespace,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if annotate := get_annotate_from_class_namespace(namespace):
|
|
53
|
+
return call_annotate_function(annotate, format=Format.FORWARDREF)
|
|
54
|
+
return namespace.get("__annotations__", {})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if find_spec("pydantic") and find_spec("pydantic.v1"):
|
|
58
|
+
from pydantic.v1.main import ModelMetaclass
|
|
59
|
+
|
|
60
|
+
class ModelMetaclassLazyAnnotations(ModelMetaclass):
|
|
61
|
+
"""Compatibility metaclass to resolve python3.14 style lazy annotations."""
|
|
62
|
+
|
|
63
|
+
def __new__(mcs, name: str, bases: tuple, namespace: dict, **kwargs):
|
|
64
|
+
"""Resolve python3.14 style lazy annotations before passing off to pydantic v1.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
name: The class name.
|
|
68
|
+
bases: The base classes.
|
|
69
|
+
namespace: The class namespace.
|
|
70
|
+
**kwargs: Additional keyword arguments.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The created class.
|
|
74
|
+
"""
|
|
75
|
+
namespace["__annotations__"] = annotations_from_namespace(namespace)
|
|
76
|
+
return super().__new__(mcs, name, bases, namespace, **kwargs)
|
|
77
|
+
else:
|
|
78
|
+
ModelMetaclassLazyAnnotations = type # type: ignore[assignment]
|
|
79
|
+
|
|
80
|
+
|
|
33
81
|
def sqlmodel_field_has_primary_key(field_info: "FieldInfo") -> bool:
|
|
34
82
|
"""Determines if a field is a primary.
|
|
35
83
|
|
reflex/utils/misc.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
|
+
import inspect
|
|
5
6
|
import sys
|
|
6
7
|
import threading
|
|
7
8
|
from collections.abc import Callable
|
|
@@ -62,7 +63,7 @@ async def run_in_thread(func: Callable) -> Any:
|
|
|
62
63
|
Returns:
|
|
63
64
|
Any: The return value of the function.
|
|
64
65
|
"""
|
|
65
|
-
if
|
|
66
|
+
if inspect.iscoroutinefunction(func):
|
|
66
67
|
msg = "func must be a non-async function"
|
|
67
68
|
raise ValueError(msg)
|
|
68
69
|
return await asyncio.get_event_loop().run_in_executor(None, func)
|
reflex/utils/monitoring.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""PyLeak integration for monitoring event loop blocking and resource leaks in Reflex applications."""
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import contextlib
|
|
5
4
|
import functools
|
|
6
5
|
import inspect
|
|
@@ -154,7 +153,7 @@ def monitor_loopblocks(func: Callable) -> Callable:
|
|
|
154
153
|
|
|
155
154
|
return async_gen_wrapper
|
|
156
155
|
|
|
157
|
-
if
|
|
156
|
+
if inspect.iscoroutinefunction(func):
|
|
158
157
|
|
|
159
158
|
@functools.wraps(func)
|
|
160
159
|
async def async_wrapper(*args, **kwargs):
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -16,9 +16,6 @@ from types import ModuleType
|
|
|
16
16
|
from typing import NamedTuple
|
|
17
17
|
|
|
18
18
|
from packaging import version
|
|
19
|
-
from redis import Redis as RedisSync
|
|
20
|
-
from redis.asyncio import Redis
|
|
21
|
-
from redis.exceptions import RedisError
|
|
22
19
|
|
|
23
20
|
from reflex import constants, model
|
|
24
21
|
from reflex.config import Config, get_config
|
|
@@ -28,6 +25,9 @@ from reflex.utils.decorator import once
|
|
|
28
25
|
from reflex.utils.misc import get_module_path
|
|
29
26
|
|
|
30
27
|
if typing.TYPE_CHECKING:
|
|
28
|
+
from redis import Redis as RedisSync
|
|
29
|
+
from redis.asyncio import Redis
|
|
30
|
+
|
|
31
31
|
from reflex.app import App
|
|
32
32
|
|
|
33
33
|
|
|
@@ -369,6 +369,12 @@ def get_redis() -> Redis | None:
|
|
|
369
369
|
Returns:
|
|
370
370
|
The asynchronous redis client.
|
|
371
371
|
"""
|
|
372
|
+
try:
|
|
373
|
+
from redis.asyncio import Redis
|
|
374
|
+
from redis.exceptions import RedisError
|
|
375
|
+
except ImportError:
|
|
376
|
+
console.debug("Redis package not installed.")
|
|
377
|
+
return None
|
|
372
378
|
if (redis_url := parse_redis_url()) is not None:
|
|
373
379
|
return Redis.from_url(
|
|
374
380
|
redis_url,
|
|
@@ -383,6 +389,12 @@ def get_redis_sync() -> RedisSync | None:
|
|
|
383
389
|
Returns:
|
|
384
390
|
The synchronous redis client.
|
|
385
391
|
"""
|
|
392
|
+
try:
|
|
393
|
+
from redis import Redis as RedisSync
|
|
394
|
+
from redis.exceptions import RedisError
|
|
395
|
+
except ImportError:
|
|
396
|
+
console.debug("Redis package not installed.")
|
|
397
|
+
return None
|
|
386
398
|
if (redis_url := parse_redis_url()) is not None:
|
|
387
399
|
return RedisSync.from_url(
|
|
388
400
|
redis_url,
|
|
@@ -417,6 +429,8 @@ async def get_redis_status() -> dict[str, bool | None]:
|
|
|
417
429
|
Returns:
|
|
418
430
|
The status of the Redis connection.
|
|
419
431
|
"""
|
|
432
|
+
from redis.exceptions import RedisError
|
|
433
|
+
|
|
420
434
|
try:
|
|
421
435
|
status = True
|
|
422
436
|
redis_client = get_redis()
|
reflex/utils/processes.py
CHANGED
|
@@ -16,7 +16,6 @@ from pathlib import Path
|
|
|
16
16
|
from typing import Any, Literal, overload
|
|
17
17
|
|
|
18
18
|
import rich.markup
|
|
19
|
-
from redis.exceptions import RedisError
|
|
20
19
|
from rich.progress import Progress
|
|
21
20
|
|
|
22
21
|
from reflex import constants
|
|
@@ -45,6 +44,9 @@ def get_num_workers() -> int:
|
|
|
45
44
|
"""
|
|
46
45
|
if (redis_client := prerequisites.get_redis_sync()) is None:
|
|
47
46
|
return 1
|
|
47
|
+
|
|
48
|
+
from redis.exceptions import RedisError
|
|
49
|
+
|
|
48
50
|
try:
|
|
49
51
|
redis_client.ping()
|
|
50
52
|
except RedisError as re:
|
reflex/utils/redir.py
CHANGED
|
@@ -1,59 +1,43 @@
|
|
|
1
1
|
"""Utilities to handle redirection to browser UI."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import webbrowser
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
from
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from urllib.parse import SplitResult
|
|
8
7
|
|
|
9
|
-
from . import console
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
def open_browser(target_url: str) -> None:
|
|
9
|
+
def open_browser(target_url: "SplitResult") -> None:
|
|
13
10
|
"""Open a browser window to target_url.
|
|
14
11
|
|
|
15
12
|
Args:
|
|
16
13
|
target_url: The URL to open in the browser.
|
|
17
14
|
"""
|
|
18
|
-
|
|
15
|
+
import webbrowser
|
|
16
|
+
|
|
17
|
+
from reflex.utils import console
|
|
18
|
+
|
|
19
|
+
if not webbrowser.open(target_url.geturl()):
|
|
19
20
|
console.warn(
|
|
20
21
|
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
|
|
21
22
|
)
|
|
22
23
|
else:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def open_browser_and_wait(target_url: str, poll_url: str, interval: int = 2):
|
|
27
|
-
"""Open a browser window to target_url and request poll_url until it returns successfully.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
target_url: The URL to open in the browser.
|
|
31
|
-
poll_url: The URL to poll for success.
|
|
32
|
-
interval: The interval in seconds to wait between polling.
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
The response from the poll_url.
|
|
36
|
-
"""
|
|
37
|
-
import httpx
|
|
38
|
-
|
|
39
|
-
open_browser(target_url)
|
|
40
|
-
console.info("[b]Complete the workflow in the browser to continue.[/b]")
|
|
41
|
-
while True:
|
|
42
|
-
try:
|
|
43
|
-
response = net.get(poll_url, follow_redirects=True)
|
|
44
|
-
if response.is_success:
|
|
45
|
-
break
|
|
46
|
-
except httpx.RequestError as err:
|
|
47
|
-
console.info(f"Will retry after error occurred while polling: {err}.")
|
|
48
|
-
time.sleep(interval)
|
|
49
|
-
return response
|
|
24
|
+
simplified_url = target_url._replace(path="", query="", fragment="").geturl()
|
|
25
|
+
console.info(f"Opened browser to {simplified_url}")
|
|
50
26
|
|
|
51
27
|
|
|
52
28
|
def reflex_build_redirect() -> None:
|
|
53
29
|
"""Open the browser window to reflex.build."""
|
|
54
|
-
|
|
30
|
+
from urllib.parse import urlsplit
|
|
31
|
+
|
|
32
|
+
from reflex import constants
|
|
33
|
+
|
|
34
|
+
open_browser(urlsplit(constants.Templates.REFLEX_BUILD_FRONTEND_WITH_REFERRER))
|
|
55
35
|
|
|
56
36
|
|
|
57
37
|
def reflex_templates():
|
|
58
38
|
"""Open the browser window to reflex.build/templates."""
|
|
59
|
-
|
|
39
|
+
from urllib.parse import urlsplit
|
|
40
|
+
|
|
41
|
+
from reflex import constants
|
|
42
|
+
|
|
43
|
+
open_browser(urlsplit(constants.Templates.REFLEX_TEMPLATES_URL))
|
reflex/utils/templates.py
CHANGED
|
@@ -419,13 +419,13 @@ def get_init_cli_prompt_options() -> list[Template]:
|
|
|
419
419
|
"""
|
|
420
420
|
return [
|
|
421
421
|
Template(
|
|
422
|
-
name=constants.Templates.
|
|
423
|
-
description="
|
|
422
|
+
name=constants.Templates.AI,
|
|
423
|
+
description="[bold]Try our free AI builder.",
|
|
424
424
|
code_url="",
|
|
425
425
|
),
|
|
426
426
|
Template(
|
|
427
|
-
name=constants.Templates.
|
|
428
|
-
description="
|
|
427
|
+
name=constants.Templates.DEFAULT,
|
|
428
|
+
description="A blank Reflex app.",
|
|
429
429
|
code_url="",
|
|
430
430
|
),
|
|
431
431
|
Template(
|
reflex/utils/types.py
CHANGED
|
@@ -291,6 +291,7 @@ def is_literal(cls: GenericType) -> bool:
|
|
|
291
291
|
return getattr(cls, "__origin__", None) is Literal
|
|
292
292
|
|
|
293
293
|
|
|
294
|
+
@lru_cache
|
|
294
295
|
def has_args(cls: type) -> bool:
|
|
295
296
|
"""Check if the class has generic parameters.
|
|
296
297
|
|
|
@@ -442,6 +443,13 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
|
|
|
442
443
|
|
|
443
444
|
from reflex.model import Model
|
|
444
445
|
|
|
446
|
+
if find_spec("sqlmodel"):
|
|
447
|
+
from sqlmodel import SQLModel
|
|
448
|
+
|
|
449
|
+
sqlmodel_types = (Model, SQLModel)
|
|
450
|
+
else:
|
|
451
|
+
sqlmodel_types = (Model,)
|
|
452
|
+
|
|
445
453
|
if isinstance(cls, type) and issubclass(cls, DeclarativeBase):
|
|
446
454
|
insp = sqlalchemy.inspect(cls)
|
|
447
455
|
if name in insp.columns:
|
|
@@ -485,7 +493,7 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
|
|
|
485
493
|
elif (
|
|
486
494
|
isinstance(cls, type)
|
|
487
495
|
and not is_generic_alias(cls)
|
|
488
|
-
and issubclass(cls,
|
|
496
|
+
and issubclass(cls, sqlmodel_types)
|
|
489
497
|
):
|
|
490
498
|
# Check in the annotations directly (for sqlmodel.Relationship)
|
|
491
499
|
hints = get_type_hints(cls) # pyright: ignore [reportArgumentType]
|
reflex/vars/base.py
CHANGED
|
@@ -43,6 +43,7 @@ from reflex import constants
|
|
|
43
43
|
from reflex.constants.compiler import Hooks
|
|
44
44
|
from reflex.constants.state import FIELD_MARKER
|
|
45
45
|
from reflex.utils import console, exceptions, imports, serializers, types
|
|
46
|
+
from reflex.utils.compat import annotations_from_namespace
|
|
46
47
|
from reflex.utils.decorator import once
|
|
47
48
|
from reflex.utils.exceptions import (
|
|
48
49
|
ComputedVarSignatureError,
|
|
@@ -1526,6 +1527,82 @@ class LiteralVar(Var):
|
|
|
1526
1527
|
def __post_init__(self):
|
|
1527
1528
|
"""Post-initialize the var."""
|
|
1528
1529
|
|
|
1530
|
+
@classmethod
|
|
1531
|
+
def _get_all_var_data_without_creating_var(
|
|
1532
|
+
cls,
|
|
1533
|
+
value: Any,
|
|
1534
|
+
) -> VarData | None:
|
|
1535
|
+
return cls.create(value)._get_all_var_data()
|
|
1536
|
+
|
|
1537
|
+
@classmethod
|
|
1538
|
+
def _get_all_var_data_without_creating_var_dispatch(
|
|
1539
|
+
cls,
|
|
1540
|
+
value: Any,
|
|
1541
|
+
) -> VarData | None:
|
|
1542
|
+
"""Get all the var data without creating a var.
|
|
1543
|
+
|
|
1544
|
+
Args:
|
|
1545
|
+
value: The value to get the var data from.
|
|
1546
|
+
|
|
1547
|
+
Returns:
|
|
1548
|
+
The var data or None.
|
|
1549
|
+
|
|
1550
|
+
Raises:
|
|
1551
|
+
TypeError: If the value is not a supported type for LiteralVar.
|
|
1552
|
+
"""
|
|
1553
|
+
from .object import LiteralObjectVar
|
|
1554
|
+
from .sequence import LiteralStringVar
|
|
1555
|
+
|
|
1556
|
+
if isinstance(value, Var):
|
|
1557
|
+
return value._get_all_var_data()
|
|
1558
|
+
|
|
1559
|
+
for literal_subclass, var_subclass in _var_literal_subclasses[::-1]:
|
|
1560
|
+
if isinstance(value, var_subclass.python_types):
|
|
1561
|
+
return literal_subclass._get_all_var_data_without_creating_var(value)
|
|
1562
|
+
|
|
1563
|
+
if (
|
|
1564
|
+
(as_var_method := getattr(value, "_as_var", None)) is not None
|
|
1565
|
+
and callable(as_var_method)
|
|
1566
|
+
and isinstance((resulting_var := as_var_method()), Var)
|
|
1567
|
+
):
|
|
1568
|
+
return resulting_var._get_all_var_data()
|
|
1569
|
+
|
|
1570
|
+
from reflex.event import EventHandler
|
|
1571
|
+
from reflex.utils.format import get_event_handler_parts
|
|
1572
|
+
|
|
1573
|
+
if isinstance(value, EventHandler):
|
|
1574
|
+
return Var(
|
|
1575
|
+
_js_expr=".".join(filter(None, get_event_handler_parts(value)))
|
|
1576
|
+
)._get_all_var_data()
|
|
1577
|
+
|
|
1578
|
+
serialized_value = serializers.serialize(value)
|
|
1579
|
+
if serialized_value is not None:
|
|
1580
|
+
if isinstance(serialized_value, Mapping):
|
|
1581
|
+
return LiteralObjectVar._get_all_var_data_without_creating_var(
|
|
1582
|
+
serialized_value
|
|
1583
|
+
)
|
|
1584
|
+
if isinstance(serialized_value, str):
|
|
1585
|
+
return LiteralStringVar._get_all_var_data_without_creating_var(
|
|
1586
|
+
serialized_value
|
|
1587
|
+
)
|
|
1588
|
+
return LiteralVar._get_all_var_data_without_creating_var_dispatch(
|
|
1589
|
+
serialized_value
|
|
1590
|
+
)
|
|
1591
|
+
|
|
1592
|
+
if dataclasses.is_dataclass(value) and not isinstance(value, type):
|
|
1593
|
+
return LiteralObjectVar._get_all_var_data_without_creating_var(
|
|
1594
|
+
{
|
|
1595
|
+
k: (None if callable(v) else v)
|
|
1596
|
+
for k, v in dataclasses.asdict(value).items()
|
|
1597
|
+
}
|
|
1598
|
+
)
|
|
1599
|
+
|
|
1600
|
+
if isinstance(value, range):
|
|
1601
|
+
return None
|
|
1602
|
+
|
|
1603
|
+
msg = f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}."
|
|
1604
|
+
raise TypeError(msg)
|
|
1605
|
+
|
|
1529
1606
|
@property
|
|
1530
1607
|
def _var_value(self) -> Any:
|
|
1531
1608
|
msg = "LiteralVar subclasses must implement the _var_value property."
|
|
@@ -1687,30 +1764,30 @@ def figure_out_type(value: Any) -> types.GenericType:
|
|
|
1687
1764
|
Returns:
|
|
1688
1765
|
The type of the value.
|
|
1689
1766
|
"""
|
|
1690
|
-
if isinstance(value, Var):
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1767
|
+
if isinstance(value, (list, set, tuple, Mapping, Var)):
|
|
1768
|
+
if isinstance(value, Var):
|
|
1769
|
+
return value._var_type
|
|
1770
|
+
if has_args(value_type := type(value)):
|
|
1771
|
+
return value_type
|
|
1772
|
+
if isinstance(value, list):
|
|
1773
|
+
if not value:
|
|
1774
|
+
return Sequence[NoReturn]
|
|
1775
|
+
return Sequence[unionize(*{figure_out_type(v) for v in value[:100]})]
|
|
1776
|
+
if isinstance(value, set):
|
|
1777
|
+
return set[unionize(*{figure_out_type(v) for v in value})]
|
|
1778
|
+
if isinstance(value, tuple):
|
|
1779
|
+
if not value:
|
|
1780
|
+
return tuple[NoReturn, ...]
|
|
1781
|
+
if len(value) <= 5:
|
|
1782
|
+
return tuple[tuple(figure_out_type(v) for v in value)]
|
|
1783
|
+
return tuple[unionize(*{figure_out_type(v) for v in value[:100]}), ...]
|
|
1784
|
+
if isinstance(value, Mapping):
|
|
1785
|
+
if not value:
|
|
1786
|
+
return Mapping[NoReturn, NoReturn]
|
|
1787
|
+
return Mapping[
|
|
1788
|
+
unionize(*{figure_out_type(k) for k in list(value.keys())[:100]}),
|
|
1789
|
+
unionize(*{figure_out_type(v) for v in list(value.values())[:100]}),
|
|
1790
|
+
]
|
|
1714
1791
|
return type(value)
|
|
1715
1792
|
|
|
1716
1793
|
|
|
@@ -2882,6 +2959,10 @@ class LiteralNoneVar(LiteralVar, NoneVar):
|
|
|
2882
2959
|
"""
|
|
2883
2960
|
return "null"
|
|
2884
2961
|
|
|
2962
|
+
@classmethod
|
|
2963
|
+
def _get_all_var_data_without_creating_var(cls, value: None) -> VarData | None:
|
|
2964
|
+
return None
|
|
2965
|
+
|
|
2885
2966
|
@classmethod
|
|
2886
2967
|
def create(
|
|
2887
2968
|
cls,
|
|
@@ -3478,7 +3559,7 @@ class BaseStateMeta(ABCMeta):
|
|
|
3478
3559
|
inherited_fields: dict[str, Field] = {}
|
|
3479
3560
|
own_fields: dict[str, Field] = {}
|
|
3480
3561
|
resolved_annotations = types.resolve_annotations(
|
|
3481
|
-
namespace
|
|
3562
|
+
annotations_from_namespace(namespace), namespace["__module__"]
|
|
3482
3563
|
)
|
|
3483
3564
|
|
|
3484
3565
|
for base in bases[::-1]:
|