reflex 0.4.4a2__py3-none-any.whl → 0.4.5__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/jinja/web/pages/stateful_component.js.jinja2 +5 -1
- reflex/.templates/web/utils/state.js +2 -1
- reflex/app.py +163 -102
- reflex/app_module_for_backend.py +4 -2
- reflex/compiler/compiler.py +103 -11
- reflex/compiler/utils.py +16 -6
- reflex/components/component.py +72 -14
- reflex/components/core/upload.py +2 -2
- reflex/components/core/upload.pyi +1 -1
- reflex/components/el/elements/forms.py +1 -1
- reflex/components/markdown/markdown.py +4 -2
- reflex/components/radix/themes/components/radio_group.py +2 -1
- reflex/components/radix/themes/components/select.py +7 -2
- reflex/components/radix/themes/components/select.pyi +14 -0
- reflex/components/radix/themes/layout/list.py +31 -4
- reflex/components/radix/themes/layout/list.pyi +53 -9
- reflex/constants/installer.py +1 -1
- reflex/constants/route.py +2 -2
- reflex/event.py +4 -0
- reflex/model.py +88 -4
- reflex/state.py +20 -5
- reflex/testing.py +21 -1
- reflex/utils/exec.py +9 -0
- reflex/utils/format.py +46 -19
- reflex/utils/prerequisites.py +31 -10
- reflex/utils/processes.py +9 -2
- reflex/utils/telemetry.py +69 -29
- reflex/vars.py +5 -3
- {reflex-0.4.4a2.dist-info → reflex-0.4.5.dist-info}/METADATA +1 -1
- {reflex-0.4.4a2.dist-info → reflex-0.4.5.dist-info}/RECORD +33 -33
- {reflex-0.4.4a2.dist-info → reflex-0.4.5.dist-info}/LICENSE +0 -0
- {reflex-0.4.4a2.dist-info → reflex-0.4.5.dist-info}/WHEEL +0 -0
- {reflex-0.4.4a2.dist-info → reflex-0.4.5.dist-info}/entry_points.txt +0 -0
reflex/state.py
CHANGED
|
@@ -472,7 +472,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
472
472
|
events[name] = value
|
|
473
473
|
|
|
474
474
|
for name, fn in events.items():
|
|
475
|
-
handler =
|
|
475
|
+
handler = cls._create_event_handler(fn)
|
|
476
476
|
cls.event_handlers[name] = handler
|
|
477
477
|
setattr(cls, name, handler)
|
|
478
478
|
|
|
@@ -677,7 +677,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
677
677
|
|
|
678
678
|
@classmethod
|
|
679
679
|
@functools.lru_cache()
|
|
680
|
-
def get_class_substate(cls, path: Sequence[str]) -> Type[BaseState]:
|
|
680
|
+
def get_class_substate(cls, path: Sequence[str] | str) -> Type[BaseState]:
|
|
681
681
|
"""Get the class substate.
|
|
682
682
|
|
|
683
683
|
Args:
|
|
@@ -689,6 +689,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
689
689
|
Raises:
|
|
690
690
|
ValueError: If the substate is not found.
|
|
691
691
|
"""
|
|
692
|
+
if isinstance(path, str):
|
|
693
|
+
path = tuple(path.split("."))
|
|
694
|
+
|
|
692
695
|
if len(path) == 0:
|
|
693
696
|
return cls
|
|
694
697
|
if path[0] == cls.get_name():
|
|
@@ -789,6 +792,18 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
789
792
|
"""
|
|
790
793
|
setattr(cls, prop._var_name, prop)
|
|
791
794
|
|
|
795
|
+
@classmethod
|
|
796
|
+
def _create_event_handler(cls, fn):
|
|
797
|
+
"""Create an event handler for the given function.
|
|
798
|
+
|
|
799
|
+
Args:
|
|
800
|
+
fn: The function to create an event handler for.
|
|
801
|
+
|
|
802
|
+
Returns:
|
|
803
|
+
The event handler.
|
|
804
|
+
"""
|
|
805
|
+
return EventHandler(fn=fn, state_full_name=cls.get_full_name())
|
|
806
|
+
|
|
792
807
|
@classmethod
|
|
793
808
|
def _create_setter(cls, prop: BaseVar):
|
|
794
809
|
"""Create a setter for the var.
|
|
@@ -798,7 +813,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
798
813
|
"""
|
|
799
814
|
setter_name = prop.get_setter_name(include_state=False)
|
|
800
815
|
if setter_name not in cls.__dict__:
|
|
801
|
-
event_handler =
|
|
816
|
+
event_handler = cls._create_event_handler(prop.get_setter())
|
|
802
817
|
cls.event_handlers[setter_name] = event_handler
|
|
803
818
|
setattr(cls, setter_name, event_handler)
|
|
804
819
|
|
|
@@ -1752,7 +1767,7 @@ class UpdateVarsInternalState(State):
|
|
|
1752
1767
|
"""
|
|
1753
1768
|
for var, value in vars.items():
|
|
1754
1769
|
state_name, _, var_name = var.rpartition(".")
|
|
1755
|
-
var_state_cls = State.get_class_substate(
|
|
1770
|
+
var_state_cls = State.get_class_substate(state_name)
|
|
1756
1771
|
var_state = await self.get_state(var_state_cls)
|
|
1757
1772
|
setattr(var_state, var_name, value)
|
|
1758
1773
|
|
|
@@ -2268,7 +2283,7 @@ class StateManagerRedis(StateManager):
|
|
|
2268
2283
|
_, state_path = _split_substate_key(token)
|
|
2269
2284
|
if state_path:
|
|
2270
2285
|
# Get the State class associated with the given path.
|
|
2271
|
-
state_cls = self.state.get_class_substate(
|
|
2286
|
+
state_cls = self.state.get_class_substate(state_path)
|
|
2272
2287
|
else:
|
|
2273
2288
|
raise RuntimeError(
|
|
2274
2289
|
"StateManagerRedis requires token to be specified in the form of {token}_{state_full_name}"
|
reflex/testing.py
CHANGED
|
@@ -211,7 +211,9 @@ class AppHarness:
|
|
|
211
211
|
# get the source from a function or module object
|
|
212
212
|
source_code = "\n".join(
|
|
213
213
|
[
|
|
214
|
-
"\n".join(
|
|
214
|
+
"\n".join(
|
|
215
|
+
self.get_app_global_source(k, v) for k, v in app_globals.items()
|
|
216
|
+
),
|
|
215
217
|
self._get_source_from_app_source(self.app_source),
|
|
216
218
|
]
|
|
217
219
|
)
|
|
@@ -331,6 +333,24 @@ class AppHarness:
|
|
|
331
333
|
self._wait_frontend()
|
|
332
334
|
return self
|
|
333
335
|
|
|
336
|
+
@staticmethod
|
|
337
|
+
def get_app_global_source(key, value):
|
|
338
|
+
"""Get the source code of a global object.
|
|
339
|
+
If value is a function or class we render the actual
|
|
340
|
+
source of value otherwise we assign value to key.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
key: variable name to assign value to.
|
|
344
|
+
value: value of the global variable.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
The rendered app global code.
|
|
348
|
+
|
|
349
|
+
"""
|
|
350
|
+
if not inspect.isclass(value) and not inspect.isfunction(value):
|
|
351
|
+
return f"{key} = {value!r}"
|
|
352
|
+
return inspect.getsource(value)
|
|
353
|
+
|
|
334
354
|
def __enter__(self) -> "AppHarness":
|
|
335
355
|
"""Contextmanager protocol for `start()`.
|
|
336
356
|
|
reflex/utils/exec.py
CHANGED
|
@@ -307,3 +307,12 @@ def is_prod_mode() -> bool:
|
|
|
307
307
|
constants.Env.DEV.value,
|
|
308
308
|
)
|
|
309
309
|
return current_mode == constants.Env.PROD.value
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def should_skip_compile() -> bool:
|
|
313
|
+
"""Whether the app should skip compile.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
True if the app should skip compile.
|
|
317
|
+
"""
|
|
318
|
+
return os.environ.get(constants.SKIP_COMPILE_ENV_VAR) == "yes"
|
reflex/utils/format.py
CHANGED
|
@@ -6,8 +6,7 @@ import inspect
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
8
|
import re
|
|
9
|
-
import
|
|
10
|
-
from typing import TYPE_CHECKING, Any, List, Union
|
|
9
|
+
from typing import TYPE_CHECKING, Any, List, Optional, Union
|
|
11
10
|
|
|
12
11
|
from reflex import constants
|
|
13
12
|
from reflex.utils import exceptions, serializers, types
|
|
@@ -161,16 +160,17 @@ def to_camel_case(text: str, allow_hyphens: bool = False) -> str:
|
|
|
161
160
|
return leading_underscores_or_hyphens + converted_word
|
|
162
161
|
|
|
163
162
|
|
|
164
|
-
def to_title_case(text: str) -> str:
|
|
163
|
+
def to_title_case(text: str, sep: str = "") -> str:
|
|
165
164
|
"""Convert a string from snake case to title case.
|
|
166
165
|
|
|
167
166
|
Args:
|
|
168
167
|
text: The string to convert.
|
|
168
|
+
sep: The separator to use to join the words.
|
|
169
169
|
|
|
170
170
|
Returns:
|
|
171
171
|
The title case string.
|
|
172
172
|
"""
|
|
173
|
-
return
|
|
173
|
+
return sep.join(word.title() for word in text.split("_"))
|
|
174
174
|
|
|
175
175
|
|
|
176
176
|
def to_kebab_case(text: str) -> str:
|
|
@@ -188,6 +188,20 @@ def to_kebab_case(text: str) -> str:
|
|
|
188
188
|
return to_snake_case(text).replace("_", "-")
|
|
189
189
|
|
|
190
190
|
|
|
191
|
+
def make_default_page_title(app_name: str, route: str) -> str:
|
|
192
|
+
"""Make a default page title from a route.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
app_name: The name of the app owning the page.
|
|
196
|
+
route: The route to make the title from.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
The default page title.
|
|
200
|
+
"""
|
|
201
|
+
title = constants.DefaultPage.TITLE.format(app_name, route)
|
|
202
|
+
return to_title_case(title, " ")
|
|
203
|
+
|
|
204
|
+
|
|
191
205
|
def _escape_js_string(string: str) -> str:
|
|
192
206
|
"""Escape the string for use as a JS string literal.
|
|
193
207
|
|
|
@@ -470,18 +484,18 @@ def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
|
|
|
470
484
|
if len(parts) == 1:
|
|
471
485
|
return ("", parts[-1])
|
|
472
486
|
|
|
473
|
-
# Get the state
|
|
474
|
-
|
|
487
|
+
# Get the state full name
|
|
488
|
+
state_full_name = handler.state_full_name
|
|
475
489
|
|
|
476
|
-
#
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
490
|
+
# Get the function name
|
|
491
|
+
name = parts[-1]
|
|
492
|
+
|
|
493
|
+
from reflex.state import State
|
|
494
|
+
|
|
495
|
+
if state_full_name == "state" and name not in State.__dict__:
|
|
482
496
|
return ("", to_snake_case(handler.fn.__qualname__))
|
|
483
497
|
|
|
484
|
-
return (
|
|
498
|
+
return (state_full_name, name)
|
|
485
499
|
|
|
486
500
|
|
|
487
501
|
def format_event_handler(handler: EventHandler) -> str:
|
|
@@ -513,9 +527,14 @@ def format_event(event_spec: EventSpec) -> str:
|
|
|
513
527
|
":".join(
|
|
514
528
|
(
|
|
515
529
|
name._var_name,
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
530
|
+
(
|
|
531
|
+
wrap(
|
|
532
|
+
json.dumps(val._var_name).strip('"').replace("`", "\\`"),
|
|
533
|
+
"`",
|
|
534
|
+
)
|
|
535
|
+
if val._var_is_string
|
|
536
|
+
else val._var_full_name
|
|
537
|
+
),
|
|
519
538
|
)
|
|
520
539
|
)
|
|
521
540
|
for name, val in event_spec.args
|
|
@@ -584,11 +603,12 @@ def format_query_params(router_data: dict[str, Any]) -> dict[str, str]:
|
|
|
584
603
|
return {k.replace("-", "_"): v for k, v in params.items()}
|
|
585
604
|
|
|
586
605
|
|
|
587
|
-
def format_state(value: Any) -> Any:
|
|
606
|
+
def format_state(value: Any, key: Optional[str] = None) -> Any:
|
|
588
607
|
"""Recursively format values in the given state.
|
|
589
608
|
|
|
590
609
|
Args:
|
|
591
610
|
value: The state to format.
|
|
611
|
+
key: The key associated with the value (optional).
|
|
592
612
|
|
|
593
613
|
Returns:
|
|
594
614
|
The formatted state.
|
|
@@ -598,7 +618,7 @@ def format_state(value: Any) -> Any:
|
|
|
598
618
|
"""
|
|
599
619
|
# Handle dicts.
|
|
600
620
|
if isinstance(value, dict):
|
|
601
|
-
return {k: format_state(v) for k, v in value.items()}
|
|
621
|
+
return {k: format_state(v, k) for k, v in value.items()}
|
|
602
622
|
|
|
603
623
|
# Handle lists, sets, typles.
|
|
604
624
|
if isinstance(value, types.StateIterBases):
|
|
@@ -613,7 +633,14 @@ def format_state(value: Any) -> Any:
|
|
|
613
633
|
if serialized is not None:
|
|
614
634
|
return serialized
|
|
615
635
|
|
|
616
|
-
|
|
636
|
+
if key is None:
|
|
637
|
+
raise TypeError(
|
|
638
|
+
f"No JSON serializer found for var {value} of type {type(value)}."
|
|
639
|
+
)
|
|
640
|
+
else:
|
|
641
|
+
raise TypeError(
|
|
642
|
+
f"No JSON serializer found for State Var '{key}' of value {value} of type {type(value)}."
|
|
643
|
+
)
|
|
617
644
|
|
|
618
645
|
|
|
619
646
|
def format_state_name(state_name: str) -> str:
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -34,6 +34,8 @@ from reflex.compiler import templates
|
|
|
34
34
|
from reflex.config import Config, get_config
|
|
35
35
|
from reflex.utils import console, path_ops, processes
|
|
36
36
|
|
|
37
|
+
CURRENTLY_INSTALLING_NODE = False
|
|
38
|
+
|
|
37
39
|
|
|
38
40
|
def check_latest_package_version(package_name: str):
|
|
39
41
|
"""Check if the latest version of the package is installed.
|
|
@@ -103,8 +105,11 @@ def get_node_version() -> version.Version | None:
|
|
|
103
105
|
Returns:
|
|
104
106
|
The version of node.
|
|
105
107
|
"""
|
|
108
|
+
node_path = path_ops.get_node_path()
|
|
109
|
+
if node_path is None:
|
|
110
|
+
return None
|
|
106
111
|
try:
|
|
107
|
-
result = processes.new_process([
|
|
112
|
+
result = processes.new_process([node_path, "-v"], run=True)
|
|
108
113
|
# The output will be in the form "vX.Y.Z", but version.parse() can handle it
|
|
109
114
|
return version.parse(result.stdout) # type: ignore
|
|
110
115
|
except (FileNotFoundError, TypeError):
|
|
@@ -211,7 +216,11 @@ def get_compiled_app(reload: bool = False) -> ModuleType:
|
|
|
211
216
|
The compiled app based on the default config.
|
|
212
217
|
"""
|
|
213
218
|
app_module = get_app(reload=reload)
|
|
214
|
-
getattr(app_module, constants.CompileVars.APP)
|
|
219
|
+
app = getattr(app_module, constants.CompileVars.APP)
|
|
220
|
+
# For py3.8 and py3.9 compatibility when redis is used, we MUST add any decorator pages
|
|
221
|
+
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
|
222
|
+
app._apply_decorated_pages()
|
|
223
|
+
app.compile_()
|
|
215
224
|
return app_module
|
|
216
225
|
|
|
217
226
|
|
|
@@ -425,19 +434,21 @@ def initialize_app_directory(app_name: str, template: constants.Templates.Kind):
|
|
|
425
434
|
)
|
|
426
435
|
|
|
427
436
|
|
|
428
|
-
def get_project_hash() -> int | None:
|
|
437
|
+
def get_project_hash(raise_on_fail: bool = False) -> int | None:
|
|
429
438
|
"""Get the project hash from the reflex.json file if the file exists.
|
|
430
439
|
|
|
440
|
+
Args:
|
|
441
|
+
raise_on_fail: Whether to raise an error if the file does not exist.
|
|
442
|
+
|
|
431
443
|
Returns:
|
|
432
444
|
project_hash: The app hash.
|
|
433
445
|
"""
|
|
434
|
-
if not os.path.exists(constants.Reflex.JSON):
|
|
446
|
+
if not os.path.exists(constants.Reflex.JSON) and not raise_on_fail:
|
|
435
447
|
return None
|
|
436
448
|
# Open and read the file
|
|
437
449
|
with open(constants.Reflex.JSON, "r") as file:
|
|
438
450
|
data = json.load(file)
|
|
439
|
-
|
|
440
|
-
return project_hash
|
|
451
|
+
return data.get("project_hash")
|
|
441
452
|
|
|
442
453
|
|
|
443
454
|
def initialize_web_directory():
|
|
@@ -611,6 +622,11 @@ def install_node():
|
|
|
611
622
|
console.debug("")
|
|
612
623
|
return
|
|
613
624
|
|
|
625
|
+
# Skip installation if check_node_version() checks out
|
|
626
|
+
if check_node_version():
|
|
627
|
+
console.debug("Skipping node installation as it is already installed.")
|
|
628
|
+
return
|
|
629
|
+
|
|
614
630
|
path_ops.mkdir(constants.Fnm.DIR)
|
|
615
631
|
if not os.path.exists(constants.Fnm.EXE):
|
|
616
632
|
download_and_extract_fnm_zip()
|
|
@@ -627,10 +643,6 @@ def install_node():
|
|
|
627
643
|
],
|
|
628
644
|
)
|
|
629
645
|
else: # All other platforms (Linux, MacOS).
|
|
630
|
-
# TODO we can skip installation if check_node_version() checks out
|
|
631
|
-
if check_node_version():
|
|
632
|
-
console.debug("Skipping node installation as it is already installed.")
|
|
633
|
-
return
|
|
634
646
|
# Add execute permissions to fnm executable.
|
|
635
647
|
os.chmod(constants.Fnm.EXE, stat.S_IXUSR)
|
|
636
648
|
# Install node.
|
|
@@ -812,6 +824,11 @@ def check_initialized(frontend: bool = True):
|
|
|
812
824
|
console.warn(
|
|
813
825
|
"""Windows Subsystem for Linux (WSL) is recommended for improving initial install times."""
|
|
814
826
|
)
|
|
827
|
+
if sys.version_info >= (3, 12):
|
|
828
|
+
console.warn(
|
|
829
|
+
"Python 3.12 on Windows has known issues with hot reload (reflex-dev/reflex#2335). "
|
|
830
|
+
"Python 3.11 is recommended with this release of Reflex."
|
|
831
|
+
)
|
|
815
832
|
|
|
816
833
|
|
|
817
834
|
def is_latest_template() -> bool:
|
|
@@ -931,8 +948,12 @@ def initialize_frontend_dependencies():
|
|
|
931
948
|
"""Initialize all the frontend dependencies."""
|
|
932
949
|
# validate dependencies before install
|
|
933
950
|
validate_frontend_dependencies()
|
|
951
|
+
# Avoid warning about Node installation while we're trying to install it.
|
|
952
|
+
global CURRENTLY_INSTALLING_NODE
|
|
953
|
+
CURRENTLY_INSTALLING_NODE = True
|
|
934
954
|
# Install the frontend dependencies.
|
|
935
955
|
processes.run_concurrently(install_node, install_bun)
|
|
956
|
+
CURRENTLY_INSTALLING_NODE = False
|
|
936
957
|
# Set up the web directory.
|
|
937
958
|
initialize_web_directory()
|
|
938
959
|
|
reflex/utils/processes.py
CHANGED
|
@@ -135,13 +135,20 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
|
|
|
135
135
|
|
|
136
136
|
Returns:
|
|
137
137
|
Execute a child program in a new process.
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
Exit: When attempting to run a command with a None value.
|
|
138
141
|
"""
|
|
139
142
|
node_bin_path = path_ops.get_node_bin_path()
|
|
140
|
-
if not node_bin_path:
|
|
143
|
+
if not node_bin_path and not prerequisites.CURRENTLY_INSTALLING_NODE:
|
|
141
144
|
console.warn(
|
|
142
145
|
"The path to the Node binary could not be found. Please ensure that Node is properly "
|
|
143
|
-
"installed and added to your system's PATH environment variable
|
|
146
|
+
"installed and added to your system's PATH environment variable or try running "
|
|
147
|
+
"`reflex init` again."
|
|
144
148
|
)
|
|
149
|
+
if None in args:
|
|
150
|
+
console.error(f"Invalid command: {args}")
|
|
151
|
+
raise typer.Exit(1)
|
|
145
152
|
# Add the node bin path to the PATH environment variable.
|
|
146
153
|
env = {
|
|
147
154
|
**os.environ,
|
reflex/utils/telemetry.py
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import json
|
|
6
5
|
import multiprocessing
|
|
7
6
|
import platform
|
|
8
7
|
from datetime import datetime
|
|
9
8
|
|
|
9
|
+
import httpx
|
|
10
10
|
import psutil
|
|
11
11
|
|
|
12
12
|
from reflex import constants
|
|
13
|
-
from reflex.utils
|
|
13
|
+
from reflex.utils import console
|
|
14
|
+
from reflex.utils.exec import should_skip_compile
|
|
15
|
+
from reflex.utils.prerequisites import ensure_reflex_installation_id, get_project_hash
|
|
14
16
|
|
|
15
17
|
POSTHOG_API_URL: str = "https://app.posthog.com/capture/"
|
|
16
18
|
|
|
@@ -57,7 +59,68 @@ def get_memory() -> int:
|
|
|
57
59
|
Returns:
|
|
58
60
|
The total memory in MB.
|
|
59
61
|
"""
|
|
60
|
-
|
|
62
|
+
try:
|
|
63
|
+
return psutil.virtual_memory().total >> 20
|
|
64
|
+
except ValueError: # needed to pass ubuntu test
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _raise_on_missing_project_hash() -> bool:
|
|
69
|
+
"""Check if an error should be raised when project hash is missing.
|
|
70
|
+
|
|
71
|
+
When running reflex with --backend-only, or doing database migration
|
|
72
|
+
operations, there is no requirement for a .web directory, so the reflex.json
|
|
73
|
+
file may not exist, and this should not be considered an error.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
False when compilation should be skipped (i.e. no .web directory is required).
|
|
77
|
+
Otherwise return True.
|
|
78
|
+
"""
|
|
79
|
+
if should_skip_compile():
|
|
80
|
+
return False
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _prepare_event(event: str) -> dict:
|
|
85
|
+
"""Prepare the event to be sent to the PostHog server.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
event: The event name.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The event data.
|
|
92
|
+
"""
|
|
93
|
+
installation_id = ensure_reflex_installation_id()
|
|
94
|
+
project_hash = get_project_hash(raise_on_fail=_raise_on_missing_project_hash())
|
|
95
|
+
|
|
96
|
+
if installation_id is None or project_hash is None:
|
|
97
|
+
console.debug(
|
|
98
|
+
f"Could not get installation_id or project_hash: {installation_id}, {project_hash}"
|
|
99
|
+
)
|
|
100
|
+
return {}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
|
|
104
|
+
"event": event,
|
|
105
|
+
"properties": {
|
|
106
|
+
"distinct_id": installation_id,
|
|
107
|
+
"distinct_app_id": project_hash,
|
|
108
|
+
"user_os": get_os(),
|
|
109
|
+
"reflex_version": get_reflex_version(),
|
|
110
|
+
"python_version": get_python_version(),
|
|
111
|
+
"cpu_count": get_cpu_count(),
|
|
112
|
+
"memory": get_memory(),
|
|
113
|
+
},
|
|
114
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _send_event(event_data: dict) -> bool:
|
|
119
|
+
try:
|
|
120
|
+
httpx.post(POSTHOG_API_URL, json=event_data)
|
|
121
|
+
return True
|
|
122
|
+
except Exception:
|
|
123
|
+
return False
|
|
61
124
|
|
|
62
125
|
|
|
63
126
|
def send(event: str, telemetry_enabled: bool | None = None) -> bool:
|
|
@@ -70,8 +133,6 @@ def send(event: str, telemetry_enabled: bool | None = None) -> bool:
|
|
|
70
133
|
Returns:
|
|
71
134
|
Whether the telemetry was sent successfully.
|
|
72
135
|
"""
|
|
73
|
-
import httpx
|
|
74
|
-
|
|
75
136
|
from reflex.config import get_config
|
|
76
137
|
|
|
77
138
|
# Get the telemetry_enabled from the config if it is not specified.
|
|
@@ -82,29 +143,8 @@ def send(event: str, telemetry_enabled: bool | None = None) -> bool:
|
|
|
82
143
|
if not telemetry_enabled:
|
|
83
144
|
return False
|
|
84
145
|
|
|
85
|
-
|
|
86
|
-
if
|
|
146
|
+
event_data = _prepare_event(event)
|
|
147
|
+
if not event_data:
|
|
87
148
|
return False
|
|
88
149
|
|
|
89
|
-
|
|
90
|
-
with open(constants.Dirs.REFLEX_JSON) as f:
|
|
91
|
-
reflex_json = json.load(f)
|
|
92
|
-
project_hash = reflex_json["project_hash"]
|
|
93
|
-
post_hog = {
|
|
94
|
-
"api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
|
|
95
|
-
"event": event,
|
|
96
|
-
"properties": {
|
|
97
|
-
"distinct_id": installation_id,
|
|
98
|
-
"distinct_app_id": project_hash,
|
|
99
|
-
"user_os": get_os(),
|
|
100
|
-
"reflex_version": get_reflex_version(),
|
|
101
|
-
"python_version": get_python_version(),
|
|
102
|
-
"cpu_count": get_cpu_count(),
|
|
103
|
-
"memory": get_memory(),
|
|
104
|
-
},
|
|
105
|
-
"timestamp": datetime.utcnow().isoformat(),
|
|
106
|
-
}
|
|
107
|
-
httpx.post(POSTHOG_API_URL, json=post_hog)
|
|
108
|
-
return True
|
|
109
|
-
except Exception:
|
|
110
|
-
return False
|
|
150
|
+
return _send_event(event_data)
|
reflex/vars.py
CHANGED
|
@@ -633,7 +633,7 @@ class Var:
|
|
|
633
633
|
if types.is_generic_alias(self._var_type):
|
|
634
634
|
index = i if not isinstance(i, Var) else 0
|
|
635
635
|
type_ = types.get_args(self._var_type)
|
|
636
|
-
type_ = type_[index % len(type_)]
|
|
636
|
+
type_ = type_[index % len(type_)] if type_ else Any
|
|
637
637
|
elif types._issubclass(self._var_type, str):
|
|
638
638
|
type_ = str
|
|
639
639
|
|
|
@@ -1449,7 +1449,7 @@ class Var:
|
|
|
1449
1449
|
return self._replace(
|
|
1450
1450
|
_var_name=f"{self._var_name}.split({other._var_full_name})",
|
|
1451
1451
|
_var_is_string=False,
|
|
1452
|
-
_var_type=
|
|
1452
|
+
_var_type=List[str],
|
|
1453
1453
|
merge_var_data=other._var_data,
|
|
1454
1454
|
)
|
|
1455
1455
|
|
|
@@ -1555,7 +1555,7 @@ class Var:
|
|
|
1555
1555
|
|
|
1556
1556
|
return BaseVar(
|
|
1557
1557
|
_var_name=f"Array.from(range({v1._var_full_name}, {v2._var_full_name}, {step._var_name}))",
|
|
1558
|
-
_var_type=
|
|
1558
|
+
_var_type=List[int],
|
|
1559
1559
|
_var_is_local=False,
|
|
1560
1560
|
_var_data=VarData.merge(
|
|
1561
1561
|
v1._var_data,
|
|
@@ -1861,6 +1861,8 @@ class ComputedVar(Var, property):
|
|
|
1861
1861
|
# handle caching
|
|
1862
1862
|
if not hasattr(instance, self._cache_attr):
|
|
1863
1863
|
setattr(instance, self._cache_attr, super().__get__(instance, owner))
|
|
1864
|
+
# Ensure the computed var gets serialized to redis.
|
|
1865
|
+
instance._was_touched = True
|
|
1864
1866
|
return getattr(instance, self._cache_attr)
|
|
1865
1867
|
|
|
1866
1868
|
def _deps(
|