reflex 0.4.9a2__py3-none-any.whl → 0.5.0a1__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/apps/blank/code/blank.py +19 -16
- reflex/.templates/apps/demo/code/pages/datatable.py +4 -4
- reflex/.templates/apps/demo/code/pages/forms.py +2 -2
- reflex/.templates/web/utils/helpers/debounce.js +17 -0
- reflex/.templates/web/utils/helpers/throttle.js +22 -0
- reflex/.templates/web/utils/state.js +21 -3
- reflex/__init__.py +6 -1
- reflex/__init__.pyi +4 -1
- reflex/app.py +157 -140
- reflex/app_module_for_backend.py +1 -1
- reflex/base.py +13 -15
- reflex/compiler/compiler.py +10 -1
- reflex/compiler/utils.py +3 -30
- reflex/components/__init__.py +1 -0
- reflex/components/chakra/datadisplay/list.py +1 -3
- reflex/components/chakra/datadisplay/list.pyi +3 -3
- reflex/components/chakra/disclosure/accordion.py +1 -1
- reflex/components/chakra/forms/pininput.pyi +1 -1
- reflex/components/chakra/media/icon.py +2 -2
- reflex/components/component.py +279 -32
- reflex/components/core/__init__.py +2 -2
- reflex/components/core/cond.py +1 -10
- reflex/components/core/debounce.py +5 -2
- reflex/components/core/debounce.pyi +4 -2
- reflex/components/core/foreach.py +1 -16
- reflex/components/core/html.py +6 -0
- reflex/components/core/match.py +2 -17
- reflex/components/core/upload.py +42 -1
- reflex/components/core/upload.pyi +199 -1
- reflex/components/datadisplay/code.py +7 -3
- reflex/components/datadisplay/code.pyi +3 -1
- reflex/components/el/elements/forms.py +1 -1
- reflex/components/el/elements/forms.pyi +1 -1
- reflex/components/lucide/icon.py +5 -13
- reflex/components/lucide/icon.pyi +0 -1
- reflex/components/markdown/markdown.py +5 -23
- reflex/components/markdown/markdown.pyi +1 -4
- reflex/components/radix/primitives/accordion.py +227 -406
- reflex/components/radix/primitives/accordion.pyi +369 -28
- reflex/components/radix/primitives/form.py +33 -29
- reflex/components/radix/primitives/form.pyi +7 -2
- reflex/components/radix/primitives/progress.py +17 -9
- reflex/components/radix/primitives/progress.pyi +2 -0
- reflex/components/radix/primitives/slider.py +30 -18
- reflex/components/radix/primitives/slider.pyi +4 -0
- reflex/components/radix/themes/base.py +8 -1
- reflex/components/radix/themes/base.pyi +79 -1
- reflex/components/radix/themes/color_mode.py +74 -30
- reflex/components/radix/themes/color_mode.pyi +26 -185
- reflex/components/radix/themes/components/__init__.py +17 -0
- reflex/components/radix/themes/components/badge.py +2 -1
- reflex/components/radix/themes/components/badge.pyi +3 -1
- reflex/components/radix/themes/components/button.py +3 -1
- reflex/components/radix/themes/components/button.pyi +4 -1
- reflex/components/radix/themes/components/checkbox_cards.py +48 -0
- reflex/components/radix/themes/components/checkbox_cards.pyi +264 -0
- reflex/components/radix/themes/components/checkbox_group.py +42 -0
- reflex/components/radix/themes/components/checkbox_group.pyi +253 -0
- reflex/components/radix/themes/components/data_list.py +63 -0
- reflex/components/radix/themes/components/data_list.pyi +426 -0
- reflex/components/radix/themes/components/icon_button.py +20 -17
- reflex/components/radix/themes/components/icon_button.pyi +5 -1
- reflex/components/radix/themes/components/progress.py +55 -0
- reflex/components/radix/themes/components/progress.pyi +180 -0
- reflex/components/radix/themes/components/radio.py +31 -0
- reflex/components/radix/themes/components/radio.pyi +169 -0
- reflex/components/radix/themes/components/radio_cards.py +48 -0
- reflex/components/radix/themes/components/radio_cards.pyi +264 -0
- reflex/components/radix/themes/components/radio_group.py +2 -4
- reflex/components/radix/themes/components/segmented_control.py +48 -0
- reflex/components/radix/themes/components/segmented_control.pyi +262 -0
- reflex/components/radix/themes/components/skeleton.py +32 -0
- reflex/components/radix/themes/components/skeleton.pyi +106 -0
- reflex/components/radix/themes/components/spinner.py +26 -0
- reflex/components/radix/themes/components/spinner.pyi +101 -0
- reflex/components/radix/themes/components/tabs.py +26 -1
- reflex/components/radix/themes/components/tabs.pyi +69 -9
- reflex/components/radix/themes/components/text_field.py +101 -71
- reflex/components/radix/themes/components/text_field.pyi +81 -499
- reflex/components/radix/themes/layout/base.py +2 -2
- reflex/components/radix/themes/layout/base.pyi +4 -4
- reflex/components/radix/themes/layout/center.py +8 -3
- reflex/components/radix/themes/layout/center.pyi +2 -1
- reflex/components/radix/themes/layout/container.py +30 -2
- reflex/components/radix/themes/layout/container.pyi +9 -30
- reflex/components/radix/themes/layout/list.py +10 -5
- reflex/components/radix/themes/layout/list.pyi +5 -21
- reflex/components/radix/themes/layout/spacer.py +8 -3
- reflex/components/radix/themes/layout/spacer.pyi +2 -1
- reflex/components/radix/themes/layout/stack.py +7 -1
- reflex/components/radix/themes/layout/stack.pyi +3 -3
- reflex/components/radix/themes/typography/link.py +10 -2
- reflex/components/radix/themes/typography/link.pyi +5 -4
- reflex/components/sonner/__init__.py +3 -0
- reflex/components/sonner/toast.py +267 -0
- reflex/components/sonner/toast.pyi +205 -0
- reflex/components/tags/iter_tag.py +9 -6
- reflex/config.py +30 -54
- reflex/constants/__init__.py +0 -2
- reflex/constants/base.py +0 -5
- reflex/constants/colors.py +2 -0
- reflex/constants/installer.py +5 -1
- reflex/constants/route.py +4 -0
- reflex/custom_components/custom_components.py +22 -1
- reflex/event.py +75 -30
- reflex/experimental/__init__.py +5 -0
- reflex/experimental/layout.py +24 -6
- reflex/model.py +2 -1
- reflex/page.py +7 -4
- reflex/reflex.py +8 -3
- reflex/route.py +39 -0
- reflex/state.py +128 -131
- reflex/style.py +20 -1
- reflex/testing.py +10 -6
- reflex/utils/console.py +3 -1
- reflex/utils/exec.py +20 -7
- reflex/utils/format.py +1 -1
- reflex/utils/imports.py +3 -1
- reflex/utils/prerequisites.py +141 -20
- reflex/utils/processes.py +21 -1
- reflex/utils/pyi_generator.py +95 -5
- reflex/utils/serializers.py +1 -1
- reflex/utils/telemetry.py +26 -4
- reflex/utils/types.py +62 -18
- reflex/vars.py +11 -5
- {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/METADATA +16 -4
- {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/RECORD +130 -108
- {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/WHEEL +1 -1
- reflex/app.pyi +0 -149
- {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/LICENSE +0 -0
- {reflex-0.4.9a2.dist-info → reflex-0.5.0a1.dist-info}/entry_points.txt +0 -0
reflex/utils/prerequisites.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import functools
|
|
5
6
|
import glob
|
|
6
7
|
import importlib
|
|
7
8
|
import inspect
|
|
@@ -49,6 +50,14 @@ class Template(Base):
|
|
|
49
50
|
demo_url: str
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
class CpuInfo(Base):
|
|
54
|
+
"""Model to save cpu info."""
|
|
55
|
+
|
|
56
|
+
manufacturer_id: Optional[str]
|
|
57
|
+
model_name: Optional[str]
|
|
58
|
+
address_width: Optional[int]
|
|
59
|
+
|
|
60
|
+
|
|
52
61
|
def check_latest_package_version(package_name: str):
|
|
53
62
|
"""Check if the latest version of the package is installed.
|
|
54
63
|
|
|
@@ -172,7 +181,7 @@ def get_install_package_manager() -> str | None:
|
|
|
172
181
|
Returns:
|
|
173
182
|
The path to the package manager.
|
|
174
183
|
"""
|
|
175
|
-
if constants.IS_WINDOWS and not
|
|
184
|
+
if constants.IS_WINDOWS and not is_windows_bun_supported():
|
|
176
185
|
return get_package_manager()
|
|
177
186
|
return get_config().bun_path
|
|
178
187
|
|
|
@@ -240,7 +249,7 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
|
|
240
249
|
# For py3.8 and py3.9 compatibility when redis is used, we MUST add any decorator pages
|
|
241
250
|
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
|
242
251
|
app._apply_decorated_pages()
|
|
243
|
-
app.
|
|
252
|
+
app._compile(export=export)
|
|
244
253
|
return app_module
|
|
245
254
|
|
|
246
255
|
|
|
@@ -285,7 +294,7 @@ def parse_redis_url() -> str | dict | None:
|
|
|
285
294
|
feature_name="host[:port] style redis urls",
|
|
286
295
|
reason="redis-py url syntax is now being used",
|
|
287
296
|
deprecation_version="0.3.6",
|
|
288
|
-
removal_version="0.
|
|
297
|
+
removal_version="0.6.0",
|
|
289
298
|
)
|
|
290
299
|
redis_url, has_port, redis_port = config.redis_url.partition(":")
|
|
291
300
|
if not has_port:
|
|
@@ -325,7 +334,7 @@ def validate_app_name(app_name: str | None = None) -> str:
|
|
|
325
334
|
app_name if app_name else os.getcwd().split(os.path.sep)[-1].replace("-", "_")
|
|
326
335
|
)
|
|
327
336
|
# Make sure the app is not named "reflex".
|
|
328
|
-
if app_name == constants.Reflex.MODULE_NAME:
|
|
337
|
+
if app_name.lower() == constants.Reflex.MODULE_NAME:
|
|
329
338
|
console.error(
|
|
330
339
|
f"The app directory cannot be named [bold]{constants.Reflex.MODULE_NAME}[/bold]."
|
|
331
340
|
)
|
|
@@ -728,7 +737,7 @@ def install_bun():
|
|
|
728
737
|
Raises:
|
|
729
738
|
FileNotFoundError: If required packages are not found.
|
|
730
739
|
"""
|
|
731
|
-
if constants.IS_WINDOWS and not
|
|
740
|
+
if constants.IS_WINDOWS and not is_windows_bun_supported():
|
|
732
741
|
console.warn(
|
|
733
742
|
"Bun for Windows is currently only available for x86 64-bit Windows. Installation will fall back on npm."
|
|
734
743
|
)
|
|
@@ -743,8 +752,15 @@ def install_bun():
|
|
|
743
752
|
# if unzip is installed
|
|
744
753
|
if constants.IS_WINDOWS:
|
|
745
754
|
processes.new_process(
|
|
746
|
-
[
|
|
747
|
-
|
|
755
|
+
[
|
|
756
|
+
"powershell",
|
|
757
|
+
"-c",
|
|
758
|
+
f"irm {constants.Bun.WINDOWS_INSTALL_URL}|iex",
|
|
759
|
+
],
|
|
760
|
+
env={
|
|
761
|
+
"BUN_INSTALL": constants.Bun.ROOT_PATH,
|
|
762
|
+
"BUN_VERSION": constants.Bun.VERSION,
|
|
763
|
+
},
|
|
748
764
|
shell=True,
|
|
749
765
|
run=True,
|
|
750
766
|
show_logs=console.is_debug(),
|
|
@@ -823,10 +839,10 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
823
839
|
"""
|
|
824
840
|
# unsupported archs(arm and 32bit machines) will use npm anyway. so we dont have to run npm twice
|
|
825
841
|
fallback_command = (
|
|
826
|
-
|
|
842
|
+
get_package_manager()
|
|
827
843
|
if not constants.IS_WINDOWS
|
|
828
844
|
or constants.IS_WINDOWS
|
|
829
|
-
and
|
|
845
|
+
and is_windows_bun_supported()
|
|
830
846
|
else None
|
|
831
847
|
)
|
|
832
848
|
processes.run_process_with_fallback(
|
|
@@ -893,10 +909,26 @@ def needs_reinit(frontend: bool = True) -> bool:
|
|
|
893
909
|
if not os.path.exists(constants.Dirs.WEB):
|
|
894
910
|
return True
|
|
895
911
|
|
|
912
|
+
# If the template is out of date, then we need to re-init
|
|
913
|
+
if not is_latest_template():
|
|
914
|
+
return True
|
|
915
|
+
|
|
896
916
|
if constants.IS_WINDOWS:
|
|
917
|
+
import uvicorn
|
|
918
|
+
|
|
919
|
+
uvi_ver = uvicorn.__version__
|
|
897
920
|
console.warn(
|
|
898
921
|
"""Windows Subsystem for Linux (WSL) is recommended for improving initial install times."""
|
|
899
922
|
)
|
|
923
|
+
if sys.version_info >= (3, 12) and uvi_ver != "0.24.0.post1":
|
|
924
|
+
console.warn(
|
|
925
|
+
f"""On Python 3.12, `uvicorn==0.24.0.post1` is recommended for improved hot reload times. Found {uvi_ver} instead."""
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
if sys.version_info < (3, 12) and uvi_ver != "0.20.0":
|
|
929
|
+
console.warn(
|
|
930
|
+
f"""On Python < 3.12, `uvicorn==0.20.0` is recommended for improved hot reload times. Found {uvi_ver} instead."""
|
|
931
|
+
)
|
|
900
932
|
# No need to reinitialize if the app is already initialized.
|
|
901
933
|
return False
|
|
902
934
|
|
|
@@ -1182,18 +1214,18 @@ def _get_rx_chakra_component_to_migrate() -> set[str]:
|
|
|
1182
1214
|
rx_chakra_object = getattr(reflex.chakra, rx_chakra_name)
|
|
1183
1215
|
try:
|
|
1184
1216
|
if (
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1217
|
+
(
|
|
1218
|
+
inspect.ismethod(rx_chakra_object)
|
|
1219
|
+
and inspect.isclass(rx_chakra_object.__self__)
|
|
1220
|
+
and issubclass(rx_chakra_object.__self__, ChakraComponent)
|
|
1221
|
+
)
|
|
1222
|
+
or (
|
|
1223
|
+
inspect.isclass(rx_chakra_object)
|
|
1224
|
+
and issubclass(rx_chakra_object, ChakraComponent)
|
|
1225
|
+
)
|
|
1226
|
+
or rx_chakra_name in whitelist
|
|
1193
1227
|
):
|
|
1194
1228
|
names_to_migrate.add(rx_chakra_name)
|
|
1195
|
-
elif rx_chakra_name in whitelist:
|
|
1196
|
-
names_to_migrate.add(rx_chakra_name)
|
|
1197
1229
|
|
|
1198
1230
|
except Exception:
|
|
1199
1231
|
raise
|
|
@@ -1406,4 +1438,93 @@ def initialize_app(app_name: str, template: str | None = None):
|
|
|
1406
1438
|
template_url=template_url,
|
|
1407
1439
|
)
|
|
1408
1440
|
|
|
1409
|
-
telemetry.send("init")
|
|
1441
|
+
telemetry.send("init", template=template)
|
|
1442
|
+
|
|
1443
|
+
|
|
1444
|
+
def format_address_width(address_width) -> int | None:
|
|
1445
|
+
"""Cast address width to an int.
|
|
1446
|
+
|
|
1447
|
+
Args:
|
|
1448
|
+
address_width: The address width.
|
|
1449
|
+
|
|
1450
|
+
Returns:
|
|
1451
|
+
Address width int
|
|
1452
|
+
"""
|
|
1453
|
+
try:
|
|
1454
|
+
return int(address_width) if address_width else None
|
|
1455
|
+
except ValueError:
|
|
1456
|
+
return None
|
|
1457
|
+
|
|
1458
|
+
|
|
1459
|
+
@functools.lru_cache(maxsize=None)
|
|
1460
|
+
def get_cpu_info() -> CpuInfo | None:
|
|
1461
|
+
"""Get the CPU info of the underlining host.
|
|
1462
|
+
|
|
1463
|
+
Returns:
|
|
1464
|
+
The CPU info.
|
|
1465
|
+
"""
|
|
1466
|
+
platform_os = platform.system()
|
|
1467
|
+
cpuinfo = {}
|
|
1468
|
+
try:
|
|
1469
|
+
if platform_os == "Windows":
|
|
1470
|
+
cmd = "wmic cpu get addresswidth,caption,manufacturer /FORMAT:csv"
|
|
1471
|
+
output = processes.execute_command_and_return_output(cmd)
|
|
1472
|
+
if output:
|
|
1473
|
+
val = output.splitlines()[-1].split(",")[1:]
|
|
1474
|
+
cpuinfo["manufacturer_id"] = val[2]
|
|
1475
|
+
cpuinfo["model_name"] = val[1].split("Family")[0].strip()
|
|
1476
|
+
cpuinfo["address_width"] = format_address_width(val[0])
|
|
1477
|
+
elif platform_os == "Linux":
|
|
1478
|
+
output = processes.execute_command_and_return_output("lscpu")
|
|
1479
|
+
if output:
|
|
1480
|
+
lines = output.split("\n")
|
|
1481
|
+
for line in lines:
|
|
1482
|
+
if "Architecture" in line:
|
|
1483
|
+
cpuinfo["address_width"] = (
|
|
1484
|
+
64 if line.split(":")[1].strip() == "x86_64" else 32
|
|
1485
|
+
)
|
|
1486
|
+
if "Vendor ID:" in line:
|
|
1487
|
+
cpuinfo["manufacturer_id"] = line.split(":")[1].strip()
|
|
1488
|
+
if "Model name" in line:
|
|
1489
|
+
cpuinfo["model_name"] = line.split(":")[1].strip()
|
|
1490
|
+
elif platform_os == "Darwin":
|
|
1491
|
+
cpuinfo["address_width"] = format_address_width(
|
|
1492
|
+
processes.execute_command_and_return_output("getconf LONG_BIT")
|
|
1493
|
+
)
|
|
1494
|
+
cpuinfo["manufacturer_id"] = processes.execute_command_and_return_output(
|
|
1495
|
+
"sysctl -n machdep.cpu.brand_string"
|
|
1496
|
+
)
|
|
1497
|
+
cpuinfo["model_name"] = processes.execute_command_and_return_output(
|
|
1498
|
+
"uname -m"
|
|
1499
|
+
)
|
|
1500
|
+
except Exception as err:
|
|
1501
|
+
console.error(f"Failed to retrieve CPU info. {err}")
|
|
1502
|
+
return None
|
|
1503
|
+
|
|
1504
|
+
return (
|
|
1505
|
+
CpuInfo(
|
|
1506
|
+
manufacturer_id=cpuinfo.get("manufacturer_id"),
|
|
1507
|
+
model_name=cpuinfo.get("model_name"),
|
|
1508
|
+
address_width=cpuinfo.get("address_width"),
|
|
1509
|
+
)
|
|
1510
|
+
if cpuinfo
|
|
1511
|
+
else None
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
@functools.lru_cache(maxsize=None)
|
|
1516
|
+
def is_windows_bun_supported() -> bool:
|
|
1517
|
+
"""Check whether the underlining host running windows qualifies to run bun.
|
|
1518
|
+
We typically do not run bun on ARM or 32 bit devices that use windows.
|
|
1519
|
+
|
|
1520
|
+
Returns:
|
|
1521
|
+
Whether the host is qualified to use bun.
|
|
1522
|
+
"""
|
|
1523
|
+
cpu_info = get_cpu_info()
|
|
1524
|
+
return (
|
|
1525
|
+
constants.IS_WINDOWS
|
|
1526
|
+
and cpu_info is not None
|
|
1527
|
+
and cpu_info.address_width == 64
|
|
1528
|
+
and cpu_info.model_name is not None
|
|
1529
|
+
and "ARM" not in cpu_info.model_name
|
|
1530
|
+
)
|
reflex/utils/processes.py
CHANGED
|
@@ -137,7 +137,9 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
|
|
|
137
137
|
# Add the node bin path to the PATH environment variable.
|
|
138
138
|
env = {
|
|
139
139
|
**os.environ,
|
|
140
|
-
"PATH": os.pathsep.join(
|
|
140
|
+
"PATH": os.pathsep.join(
|
|
141
|
+
[node_bin_path if node_bin_path else "", os.environ["PATH"]]
|
|
142
|
+
), # type: ignore
|
|
141
143
|
**kwargs.pop("env", {}),
|
|
142
144
|
}
|
|
143
145
|
kwargs = {
|
|
@@ -345,3 +347,21 @@ def run_process_with_fallback(args, *, show_status_message, fallback=None, **kwa
|
|
|
345
347
|
fallback=None,
|
|
346
348
|
**kwargs,
|
|
347
349
|
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def execute_command_and_return_output(command) -> str | None:
|
|
353
|
+
"""Execute a command and return the output.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
command: The command to run.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
The output of the command.
|
|
360
|
+
"""
|
|
361
|
+
try:
|
|
362
|
+
return subprocess.check_output(command, shell=True).decode().strip()
|
|
363
|
+
except subprocess.SubprocessError as err:
|
|
364
|
+
console.error(
|
|
365
|
+
f"The command `{command}` failed with error: {err}. This will return None."
|
|
366
|
+
)
|
|
367
|
+
return None
|
reflex/utils/pyi_generator.py
CHANGED
|
@@ -34,7 +34,7 @@ PWD = Path(".").resolve()
|
|
|
34
34
|
|
|
35
35
|
EXCLUDED_FILES = [
|
|
36
36
|
"__init__.py",
|
|
37
|
-
"app.py",
|
|
37
|
+
# "app.py",
|
|
38
38
|
"component.py",
|
|
39
39
|
"bare.py",
|
|
40
40
|
"foreach.py",
|
|
@@ -117,6 +117,29 @@ def _get_type_hint(value, type_hint_globals, is_optional=True) -> str:
|
|
|
117
117
|
"""
|
|
118
118
|
res = ""
|
|
119
119
|
args = get_args(value)
|
|
120
|
+
|
|
121
|
+
if value is type(None):
|
|
122
|
+
return "None"
|
|
123
|
+
|
|
124
|
+
if rx_types.is_union(value):
|
|
125
|
+
if type(None) in value.__args__:
|
|
126
|
+
res_args = [
|
|
127
|
+
_get_type_hint(arg, type_hint_globals, rx_types.is_optional(arg))
|
|
128
|
+
for arg in value.__args__
|
|
129
|
+
if arg is not type(None)
|
|
130
|
+
]
|
|
131
|
+
if len(res_args) == 1:
|
|
132
|
+
return f"Optional[{res_args[0]}]"
|
|
133
|
+
else:
|
|
134
|
+
res = f"Union[{', '.join(res_args)}]"
|
|
135
|
+
return f"Optional[{res}]"
|
|
136
|
+
|
|
137
|
+
res_args = [
|
|
138
|
+
_get_type_hint(arg, type_hint_globals, rx_types.is_optional(arg))
|
|
139
|
+
for arg in value.__args__
|
|
140
|
+
]
|
|
141
|
+
return f"Union[{', '.join(res_args)}]"
|
|
142
|
+
|
|
120
143
|
if args:
|
|
121
144
|
inner_container_type_args = (
|
|
122
145
|
[repr(arg) for arg in args]
|
|
@@ -141,6 +164,20 @@ def _get_type_hint(value, type_hint_globals, is_optional=True) -> str:
|
|
|
141
164
|
res = f"Union[{res}]"
|
|
142
165
|
elif isinstance(value, str):
|
|
143
166
|
ev = eval(value, type_hint_globals)
|
|
167
|
+
if rx_types.is_optional(ev):
|
|
168
|
+
# hints = {
|
|
169
|
+
# _get_type_hint(arg, type_hint_globals, is_optional=False)
|
|
170
|
+
# for arg in ev.__args__
|
|
171
|
+
# }
|
|
172
|
+
return _get_type_hint(ev, type_hint_globals, is_optional=False)
|
|
173
|
+
# return f"Optional[{', '.join(hints)}]"
|
|
174
|
+
|
|
175
|
+
if rx_types.is_union(ev):
|
|
176
|
+
res = [
|
|
177
|
+
_get_type_hint(arg, type_hint_globals, rx_types.is_optional(arg))
|
|
178
|
+
for arg in ev.__args__
|
|
179
|
+
]
|
|
180
|
+
return f"Union[{', '.join(res)}]"
|
|
144
181
|
res = (
|
|
145
182
|
_get_type_hint(ev, type_hint_globals, is_optional=False)
|
|
146
183
|
if ev.__name__ == "Var"
|
|
@@ -424,7 +461,58 @@ def _generate_component_create_functiondef(
|
|
|
424
461
|
return definition
|
|
425
462
|
|
|
426
463
|
|
|
464
|
+
def _generate_staticmethod_call_functiondef(
|
|
465
|
+
node: ast.FunctionDef | None,
|
|
466
|
+
clz: type[Component] | type[SimpleNamespace],
|
|
467
|
+
type_hint_globals: dict[str, Any],
|
|
468
|
+
) -> ast.FunctionDef | None:
|
|
469
|
+
...
|
|
470
|
+
|
|
471
|
+
fullspec = getfullargspec(clz.__call__)
|
|
472
|
+
|
|
473
|
+
call_args = ast.arguments(
|
|
474
|
+
args=[
|
|
475
|
+
ast.arg(
|
|
476
|
+
name,
|
|
477
|
+
annotation=ast.Name(
|
|
478
|
+
id=_get_type_hint(
|
|
479
|
+
anno := fullspec.annotations[name],
|
|
480
|
+
type_hint_globals,
|
|
481
|
+
is_optional=rx_types.is_optional(anno),
|
|
482
|
+
)
|
|
483
|
+
),
|
|
484
|
+
)
|
|
485
|
+
for name in fullspec.args
|
|
486
|
+
],
|
|
487
|
+
posonlyargs=[],
|
|
488
|
+
kwonlyargs=[],
|
|
489
|
+
kw_defaults=[],
|
|
490
|
+
kwarg=ast.arg(arg="props"),
|
|
491
|
+
defaults=[],
|
|
492
|
+
)
|
|
493
|
+
definition = ast.FunctionDef(
|
|
494
|
+
name="__call__",
|
|
495
|
+
args=call_args,
|
|
496
|
+
body=[
|
|
497
|
+
ast.Expr(value=ast.Constant(value=clz.__call__.__doc__)),
|
|
498
|
+
ast.Expr(
|
|
499
|
+
value=ast.Constant(...),
|
|
500
|
+
),
|
|
501
|
+
],
|
|
502
|
+
decorator_list=[ast.Name(id="staticmethod")],
|
|
503
|
+
lineno=node.lineno if node is not None else None,
|
|
504
|
+
returns=ast.Constant(
|
|
505
|
+
value=_get_type_hint(
|
|
506
|
+
typing.get_type_hints(clz.__call__).get("return", None),
|
|
507
|
+
type_hint_globals,
|
|
508
|
+
)
|
|
509
|
+
),
|
|
510
|
+
)
|
|
511
|
+
return definition
|
|
512
|
+
|
|
513
|
+
|
|
427
514
|
def _generate_namespace_call_functiondef(
|
|
515
|
+
node: ast.ClassDef | None,
|
|
428
516
|
clz_name: str,
|
|
429
517
|
classes: dict[str, type[Component] | type[SimpleNamespace]],
|
|
430
518
|
type_hint_globals: dict[str, Any],
|
|
@@ -432,6 +520,7 @@ def _generate_namespace_call_functiondef(
|
|
|
432
520
|
"""Generate the __call__ function definition for a SimpleNamespace.
|
|
433
521
|
|
|
434
522
|
Args:
|
|
523
|
+
node: The existing __call__ classdef parent node from the ast
|
|
435
524
|
clz_name: The name of the SimpleNamespace class to generate the __call__ functiondef for.
|
|
436
525
|
classes: Map name to actual class definition.
|
|
437
526
|
type_hint_globals: The globals to use to resolving a type hint str.
|
|
@@ -446,10 +535,12 @@ def _generate_namespace_call_functiondef(
|
|
|
446
535
|
|
|
447
536
|
clz = classes[clz_name]
|
|
448
537
|
|
|
538
|
+
if not hasattr(clz.__call__, "__self__"):
|
|
539
|
+
return _generate_staticmethod_call_functiondef(node, clz, type_hint_globals) # type: ignore
|
|
540
|
+
|
|
449
541
|
# Determine which class is wrapped by the namespace __call__ method
|
|
450
542
|
component_clz = clz.__call__.__self__
|
|
451
543
|
|
|
452
|
-
# Only generate for create functions
|
|
453
544
|
if clz.__call__.__func__.__name__ != "create":
|
|
454
545
|
return None
|
|
455
546
|
|
|
@@ -603,6 +694,7 @@ class StubGenerator(ast.NodeTransformer):
|
|
|
603
694
|
if not child.targets[:]:
|
|
604
695
|
node.body.remove(child)
|
|
605
696
|
call_definition = _generate_namespace_call_functiondef(
|
|
697
|
+
node,
|
|
606
698
|
self.current_class,
|
|
607
699
|
self.classes,
|
|
608
700
|
type_hint_globals=self.type_hint_globals,
|
|
@@ -738,9 +830,7 @@ class PyiGenerator:
|
|
|
738
830
|
mode=black.mode.Mode(is_pyi=True),
|
|
739
831
|
).splitlines():
|
|
740
832
|
# Bit of a hack here, since the AST cannot represent comments.
|
|
741
|
-
if "def create(" in formatted_line:
|
|
742
|
-
pyi_content.append(formatted_line + " # type: ignore")
|
|
743
|
-
elif "Figure" in formatted_line:
|
|
833
|
+
if "def create(" in formatted_line or "Figure" in formatted_line:
|
|
744
834
|
pyi_content.append(formatted_line + " # type: ignore")
|
|
745
835
|
else:
|
|
746
836
|
pyi_content.append(formatted_line)
|
reflex/utils/serializers.py
CHANGED
|
@@ -359,7 +359,7 @@ try:
|
|
|
359
359
|
mime_type = MIME[image_format]
|
|
360
360
|
except KeyError:
|
|
361
361
|
# Unknown mime_type: warn and return image/png and hope the browser can sort it out.
|
|
362
|
-
warnings.warn(
|
|
362
|
+
warnings.warn( # noqa: B028
|
|
363
363
|
f"Unknown mime type for {image} {image_format}. Defaulting to image/png"
|
|
364
364
|
)
|
|
365
365
|
mime_type = "image/png"
|
reflex/utils/telemetry.py
CHANGED
|
@@ -32,6 +32,15 @@ def get_os() -> str:
|
|
|
32
32
|
return platform.system()
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def get_detailed_platform_str() -> str:
|
|
36
|
+
"""Get the detailed os/platform string.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The platform string
|
|
40
|
+
"""
|
|
41
|
+
return platform.platform()
|
|
42
|
+
|
|
43
|
+
|
|
35
44
|
def get_python_version() -> str:
|
|
36
45
|
"""Get the Python version.
|
|
37
46
|
|
|
@@ -87,15 +96,18 @@ def _raise_on_missing_project_hash() -> bool:
|
|
|
87
96
|
return True
|
|
88
97
|
|
|
89
98
|
|
|
90
|
-
def _prepare_event(event: str) -> dict:
|
|
99
|
+
def _prepare_event(event: str, **kwargs) -> dict:
|
|
91
100
|
"""Prepare the event to be sent to the PostHog server.
|
|
92
101
|
|
|
93
102
|
Args:
|
|
94
103
|
event: The event name.
|
|
104
|
+
kwargs: Additional data to send with the event.
|
|
95
105
|
|
|
96
106
|
Returns:
|
|
97
107
|
The event data.
|
|
98
108
|
"""
|
|
109
|
+
from reflex.utils.prerequisites import get_cpu_info
|
|
110
|
+
|
|
99
111
|
installation_id = ensure_reflex_installation_id()
|
|
100
112
|
project_hash = get_project_hash(raise_on_fail=_raise_on_missing_project_hash())
|
|
101
113
|
|
|
@@ -111,6 +123,9 @@ def _prepare_event(event: str) -> dict:
|
|
|
111
123
|
else:
|
|
112
124
|
# for python 3.11 & 3.12
|
|
113
125
|
stamp = datetime.now(UTC).isoformat()
|
|
126
|
+
|
|
127
|
+
cpuinfo = get_cpu_info()
|
|
128
|
+
|
|
114
129
|
return {
|
|
115
130
|
"api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
|
|
116
131
|
"event": event,
|
|
@@ -118,10 +133,17 @@ def _prepare_event(event: str) -> dict:
|
|
|
118
133
|
"distinct_id": installation_id,
|
|
119
134
|
"distinct_app_id": project_hash,
|
|
120
135
|
"user_os": get_os(),
|
|
136
|
+
"user_os_detail": get_detailed_platform_str(),
|
|
121
137
|
"reflex_version": get_reflex_version(),
|
|
122
138
|
"python_version": get_python_version(),
|
|
123
139
|
"cpu_count": get_cpu_count(),
|
|
124
140
|
"memory": get_memory(),
|
|
141
|
+
"cpu_info": dict(cpuinfo) if cpuinfo else {},
|
|
142
|
+
**(
|
|
143
|
+
{"template": template}
|
|
144
|
+
if (template := kwargs.get("template")) is not None
|
|
145
|
+
else {}
|
|
146
|
+
),
|
|
125
147
|
},
|
|
126
148
|
"timestamp": stamp,
|
|
127
149
|
}
|
|
@@ -135,12 +157,13 @@ def _send_event(event_data: dict) -> bool:
|
|
|
135
157
|
return False
|
|
136
158
|
|
|
137
159
|
|
|
138
|
-
def send(event: str, telemetry_enabled: bool | None = None) -> bool:
|
|
160
|
+
def send(event: str, telemetry_enabled: bool | None = None, **kwargs) -> bool:
|
|
139
161
|
"""Send anonymous telemetry for Reflex.
|
|
140
162
|
|
|
141
163
|
Args:
|
|
142
164
|
event: The event name.
|
|
143
165
|
telemetry_enabled: Whether to send the telemetry (If None, get from config).
|
|
166
|
+
kwargs: Additional data to send with the event.
|
|
144
167
|
|
|
145
168
|
Returns:
|
|
146
169
|
Whether the telemetry was sent successfully.
|
|
@@ -155,8 +178,7 @@ def send(event: str, telemetry_enabled: bool | None = None) -> bool:
|
|
|
155
178
|
if not telemetry_enabled:
|
|
156
179
|
return False
|
|
157
180
|
|
|
158
|
-
event_data = _prepare_event(event)
|
|
181
|
+
event_data = _prepare_event(event, **kwargs)
|
|
159
182
|
if not event_data:
|
|
160
183
|
return False
|
|
161
|
-
|
|
162
184
|
return _send_event(event_data)
|
reflex/utils/types.py
CHANGED
|
@@ -4,16 +4,18 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import contextlib
|
|
6
6
|
import inspect
|
|
7
|
+
import sys
|
|
7
8
|
import types
|
|
8
9
|
from functools import wraps
|
|
9
10
|
from typing import (
|
|
10
|
-
TYPE_CHECKING,
|
|
11
11
|
Any,
|
|
12
12
|
Callable,
|
|
13
|
+
Dict,
|
|
13
14
|
Iterable,
|
|
14
15
|
List,
|
|
15
16
|
Literal,
|
|
16
17
|
Optional,
|
|
18
|
+
Tuple,
|
|
17
19
|
Type,
|
|
18
20
|
Union,
|
|
19
21
|
_GenericAlias, # type: ignore
|
|
@@ -25,23 +27,22 @@ from typing import (
|
|
|
25
27
|
import sqlalchemy
|
|
26
28
|
|
|
27
29
|
try:
|
|
28
|
-
|
|
29
|
-
# reflex-hosting-cli tools are compatible with pydantic v2
|
|
30
|
-
|
|
31
|
-
if not TYPE_CHECKING:
|
|
32
|
-
from pydantic.v1.fields import ModelField
|
|
33
|
-
else:
|
|
34
|
-
raise ModuleNotFoundError
|
|
30
|
+
from pydantic.v1.fields import ModelField
|
|
35
31
|
except ModuleNotFoundError:
|
|
36
|
-
from pydantic.fields import ModelField
|
|
32
|
+
from pydantic.fields import ModelField # type: ignore
|
|
37
33
|
|
|
38
34
|
from sqlalchemy.ext.associationproxy import AssociationProxyInstance
|
|
39
35
|
from sqlalchemy.ext.hybrid import hybrid_property
|
|
40
|
-
from sqlalchemy.orm import
|
|
36
|
+
from sqlalchemy.orm import (
|
|
37
|
+
DeclarativeBase,
|
|
38
|
+
Mapped,
|
|
39
|
+
QueryableAttribute,
|
|
40
|
+
Relationship,
|
|
41
|
+
)
|
|
41
42
|
|
|
42
43
|
from reflex import constants
|
|
43
44
|
from reflex.base import Base
|
|
44
|
-
from reflex.utils import serializers
|
|
45
|
+
from reflex.utils import console, serializers
|
|
45
46
|
|
|
46
47
|
# Potential GenericAlias types for isinstance checks.
|
|
47
48
|
GenericAliasTypes = [_GenericAlias]
|
|
@@ -76,6 +77,13 @@ StateIterVar = Union[list, set, tuple]
|
|
|
76
77
|
ArgsSpec = Callable
|
|
77
78
|
|
|
78
79
|
|
|
80
|
+
PrimitiveToAnnotation = {
|
|
81
|
+
list: List,
|
|
82
|
+
tuple: Tuple,
|
|
83
|
+
dict: Dict,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
79
87
|
class Unset:
|
|
80
88
|
"""A class to represent an unset value.
|
|
81
89
|
|
|
@@ -111,6 +119,18 @@ def is_generic_alias(cls: GenericType) -> bool:
|
|
|
111
119
|
return isinstance(cls, GenericAliasTypes)
|
|
112
120
|
|
|
113
121
|
|
|
122
|
+
def is_none(cls: GenericType) -> bool:
|
|
123
|
+
"""Check if a class is None.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
cls: The class to check.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Whether the class is None.
|
|
130
|
+
"""
|
|
131
|
+
return cls is type(None) or cls is None
|
|
132
|
+
|
|
133
|
+
|
|
114
134
|
def is_union(cls: GenericType) -> bool:
|
|
115
135
|
"""Check if a class is a Union.
|
|
116
136
|
|
|
@@ -192,8 +212,20 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
|
|
|
192
212
|
elif isinstance(cls, type) and issubclass(cls, DeclarativeBase):
|
|
193
213
|
insp = sqlalchemy.inspect(cls)
|
|
194
214
|
if name in insp.columns:
|
|
195
|
-
|
|
196
|
-
|
|
215
|
+
# check for list types
|
|
216
|
+
column = insp.columns[name]
|
|
217
|
+
column_type = column.type
|
|
218
|
+
type_ = insp.columns[name].type.python_type
|
|
219
|
+
if hasattr(column_type, "item_type") and (
|
|
220
|
+
item_type := column_type.item_type.python_type # type: ignore
|
|
221
|
+
):
|
|
222
|
+
if type_ in PrimitiveToAnnotation:
|
|
223
|
+
type_ = PrimitiveToAnnotation[type_] # type: ignore
|
|
224
|
+
type_ = type_[item_type] # type: ignore
|
|
225
|
+
if column.nullable:
|
|
226
|
+
type_ = Optional[type_]
|
|
227
|
+
return type_
|
|
228
|
+
if name not in insp.all_orm_descriptors:
|
|
197
229
|
return None
|
|
198
230
|
descriptor = insp.all_orm_descriptors[name]
|
|
199
231
|
if hint := get_property_hint(descriptor):
|
|
@@ -202,11 +234,10 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
|
|
|
202
234
|
prop = descriptor.property
|
|
203
235
|
if not isinstance(prop, Relationship):
|
|
204
236
|
return None
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return class_
|
|
237
|
+
type_ = prop.mapper.class_
|
|
238
|
+
# TODO: check for nullable?
|
|
239
|
+
type_ = List[type_] if prop.uselist else Optional[type_]
|
|
240
|
+
return type_
|
|
210
241
|
if isinstance(attr, AssociationProxyInstance):
|
|
211
242
|
return List[
|
|
212
243
|
get_attribute_access_type(
|
|
@@ -232,6 +263,19 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None
|
|
|
232
263
|
if type_ is not None:
|
|
233
264
|
# Return the first attribute type that is accessible.
|
|
234
265
|
return type_
|
|
266
|
+
elif isinstance(cls, type):
|
|
267
|
+
# Bare class
|
|
268
|
+
if sys.version_info >= (3, 10):
|
|
269
|
+
exceptions = NameError
|
|
270
|
+
else:
|
|
271
|
+
exceptions = (NameError, TypeError)
|
|
272
|
+
try:
|
|
273
|
+
hints = get_type_hints(cls)
|
|
274
|
+
if name in hints:
|
|
275
|
+
return hints[name]
|
|
276
|
+
except exceptions as e:
|
|
277
|
+
console.warn(f"Failed to resolve ForwardRefs for {cls}.{name} due to {e}")
|
|
278
|
+
pass
|
|
235
279
|
return None # Attribute is not accessible.
|
|
236
280
|
|
|
237
281
|
|