reflex 0.7.0a4__py3-none-any.whl → 0.7.1__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/package.json.jinja2 +7 -1
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -1
- reflex/__init__.py +1 -0
- reflex/__init__.pyi +1 -0
- reflex/app.py +269 -86
- reflex/base.py +4 -10
- reflex/compiler/compiler.py +46 -12
- reflex/compiler/templates.py +1 -2
- reflex/compiler/utils.py +23 -14
- reflex/components/base/bare.py +109 -16
- reflex/components/component.py +179 -124
- reflex/components/core/__init__.py +1 -0
- reflex/components/core/__init__.pyi +1 -0
- reflex/components/core/auto_scroll.py +114 -0
- reflex/components/core/auto_scroll.pyi +284 -0
- reflex/components/core/banner.py +40 -9
- reflex/components/core/banner.pyi +400 -87
- reflex/components/core/breakpoints.py +1 -1
- reflex/components/core/cond.py +0 -8
- reflex/components/core/foreach.py +12 -2
- reflex/components/core/html.pyi +200 -19
- reflex/components/core/match.py +4 -4
- reflex/components/core/sticky.py +4 -30
- reflex/components/core/sticky.pyi +874 -90
- reflex/components/core/upload.py +3 -5
- reflex/components/core/upload.pyi +2 -4
- reflex/components/datadisplay/code.py +36 -10
- reflex/components/datadisplay/code.pyi +1 -1
- reflex/components/datadisplay/dataeditor.py +1 -3
- reflex/components/datadisplay/dataeditor.pyi +1 -3
- reflex/components/el/elements/base.py +95 -17
- reflex/components/el/elements/base.pyi +278 -19
- reflex/components/el/elements/forms.py +124 -102
- reflex/components/el/elements/forms.pyi +2787 -365
- reflex/components/el/elements/inline.py +24 -15
- reflex/components/el/elements/inline.pyi +5655 -546
- reflex/components/el/elements/media.py +79 -95
- reflex/components/el/elements/media.pyi +5167 -565
- reflex/components/el/elements/metadata.py +19 -17
- reflex/components/el/elements/metadata.pyi +841 -89
- reflex/components/el/elements/other.py +3 -5
- reflex/components/el/elements/other.pyi +1404 -137
- reflex/components/el/elements/scripts.py +10 -13
- reflex/components/el/elements/scripts.pyi +634 -65
- reflex/components/el/elements/sectioning.pyi +3001 -286
- reflex/components/el/elements/tables.py +14 -35
- reflex/components/el/elements/tables.pyi +2029 -218
- reflex/components/el/elements/typography.py +10 -13
- reflex/components/el/elements/typography.pyi +3014 -297
- reflex/components/lucide/icon.py +22 -6
- reflex/components/markdown/markdown.py +30 -10
- reflex/components/markdown/markdown.pyi +3 -2
- reflex/components/plotly/plotly.py +1 -3
- reflex/components/plotly/plotly.pyi +1 -3
- reflex/components/radix/primitives/form.pyi +624 -93
- reflex/components/radix/themes/color_mode.py +1 -1
- reflex/components/radix/themes/color_mode.pyi +213 -31
- reflex/components/radix/themes/components/alert_dialog.pyi +199 -18
- reflex/components/radix/themes/components/badge.pyi +199 -18
- reflex/components/radix/themes/components/button.pyi +213 -31
- reflex/components/radix/themes/components/callout.pyi +1000 -95
- reflex/components/radix/themes/components/card.pyi +199 -18
- reflex/components/radix/themes/components/context_menu.py +79 -1
- reflex/components/radix/themes/components/context_menu.pyi +320 -1
- reflex/components/radix/themes/components/dialog.pyi +199 -18
- reflex/components/radix/themes/components/hover_card.pyi +199 -18
- reflex/components/radix/themes/components/icon_button.pyi +213 -31
- reflex/components/radix/themes/components/inset.pyi +199 -18
- reflex/components/radix/themes/components/popover.pyi +199 -18
- reflex/components/radix/themes/components/table.pyi +1437 -154
- reflex/components/radix/themes/components/text_area.py +2 -2
- reflex/components/radix/themes/components/text_area.pyi +201 -20
- reflex/components/radix/themes/components/text_field.py +1 -1
- reflex/components/radix/themes/components/text_field.pyi +444 -88
- reflex/components/radix/themes/layout/box.pyi +200 -19
- reflex/components/radix/themes/layout/center.pyi +199 -18
- reflex/components/radix/themes/layout/container.pyi +199 -18
- reflex/components/radix/themes/layout/flex.pyi +199 -18
- reflex/components/radix/themes/layout/grid.pyi +199 -18
- reflex/components/radix/themes/layout/list.pyi +604 -57
- reflex/components/radix/themes/layout/section.pyi +199 -18
- reflex/components/radix/themes/layout/spacer.pyi +199 -18
- reflex/components/radix/themes/layout/stack.pyi +597 -54
- reflex/components/radix/themes/typography/blockquote.pyi +200 -19
- reflex/components/radix/themes/typography/code.pyi +199 -18
- reflex/components/radix/themes/typography/heading.pyi +199 -18
- reflex/components/radix/themes/typography/link.pyi +238 -28
- reflex/components/radix/themes/typography/text.pyi +1394 -127
- reflex/components/react_player/react_player.py +1 -1
- reflex/components/react_player/react_player.pyi +1 -3
- reflex/components/sonner/toast.py +41 -12
- reflex/components/sonner/toast.pyi +20 -6
- reflex/components/tags/iter_tag.py +4 -0
- reflex/components/tags/tag.py +3 -3
- reflex/config.py +187 -28
- reflex/constants/__init__.py +2 -0
- reflex/constants/base.py +6 -0
- reflex/constants/compiler.py +9 -0
- reflex/constants/event.py +1 -0
- reflex/constants/installer.py +8 -5
- reflex/constants/utils.py +1 -3
- reflex/event.py +7 -16
- reflex/experimental/layout.pyi +597 -54
- reflex/py.typed +0 -0
- reflex/reflex.py +44 -48
- reflex/state.py +49 -44
- reflex/style.py +15 -22
- reflex/testing.py +2 -0
- reflex/utils/build.py +12 -0
- reflex/utils/console.py +4 -0
- reflex/utils/decorator.py +25 -0
- reflex/utils/exec.py +92 -34
- reflex/utils/format.py +35 -6
- reflex/utils/path_ops.py +32 -1
- reflex/utils/prerequisites.py +45 -35
- reflex/utils/processes.py +12 -13
- reflex/utils/serializers.py +20 -43
- reflex/utils/telemetry.py +4 -15
- reflex/utils/types.py +36 -66
- reflex/vars/base.py +53 -76
- reflex/vars/function.py +17 -5
- reflex/vars/number.py +1 -1
- reflex/vars/sequence.py +80 -4
- {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/METADATA +4 -5
- {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/RECORD +128 -124
- {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/LICENSE +0 -0
- {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/WHEEL +0 -0
- {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/entry_points.txt +0 -0
reflex/utils/exec.py
CHANGED
|
@@ -10,6 +10,7 @@ import re
|
|
|
10
10
|
import subprocess
|
|
11
11
|
import sys
|
|
12
12
|
from pathlib import Path
|
|
13
|
+
from typing import Sequence
|
|
13
14
|
from urllib.parse import urljoin
|
|
14
15
|
|
|
15
16
|
import psutil
|
|
@@ -242,29 +243,63 @@ def run_backend(
|
|
|
242
243
|
run_uvicorn_backend(host, port, loglevel)
|
|
243
244
|
|
|
244
245
|
|
|
245
|
-
def
|
|
246
|
-
"""Get the reload
|
|
246
|
+
def get_reload_paths() -> Sequence[Path]:
|
|
247
|
+
"""Get the reload paths for the backend.
|
|
247
248
|
|
|
248
249
|
Returns:
|
|
249
|
-
The reload
|
|
250
|
+
The reload paths for the backend.
|
|
250
251
|
"""
|
|
251
252
|
config = get_config()
|
|
252
|
-
|
|
253
|
+
reload_paths = [Path(config.app_name).parent]
|
|
253
254
|
if config.app_module is not None and config.app_module.__file__:
|
|
254
255
|
module_path = Path(config.app_module.__file__).resolve().parent
|
|
255
256
|
|
|
256
|
-
while module_path.parent.name
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
module_path = module_path.parent
|
|
263
|
-
else:
|
|
264
|
-
break
|
|
257
|
+
while module_path.parent.name and any(
|
|
258
|
+
sibling_file.name == "__init__.py"
|
|
259
|
+
for sibling_file in module_path.parent.iterdir()
|
|
260
|
+
):
|
|
261
|
+
# go up a level to find dir without `__init__.py`
|
|
262
|
+
module_path = module_path.parent
|
|
265
263
|
|
|
266
|
-
|
|
267
|
-
|
|
264
|
+
reload_paths = [module_path]
|
|
265
|
+
|
|
266
|
+
include_dirs = tuple(
|
|
267
|
+
map(Path.absolute, environment.REFLEX_HOT_RELOAD_INCLUDE_PATHS.get())
|
|
268
|
+
)
|
|
269
|
+
exclude_dirs = tuple(
|
|
270
|
+
map(Path.absolute, environment.REFLEX_HOT_RELOAD_EXCLUDE_PATHS.get())
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def is_excluded_by_default(path: Path) -> bool:
|
|
274
|
+
if path.is_dir():
|
|
275
|
+
if path.name.startswith("."):
|
|
276
|
+
# exclude hidden directories
|
|
277
|
+
return True
|
|
278
|
+
if path.name.startswith("__"):
|
|
279
|
+
# ignore things like __pycache__
|
|
280
|
+
return True
|
|
281
|
+
return path.name in (".gitignore", "uploaded_files")
|
|
282
|
+
|
|
283
|
+
reload_paths = (
|
|
284
|
+
tuple(
|
|
285
|
+
path.absolute()
|
|
286
|
+
for dir in reload_paths
|
|
287
|
+
for path in dir.iterdir()
|
|
288
|
+
if not is_excluded_by_default(path)
|
|
289
|
+
)
|
|
290
|
+
+ include_dirs
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if exclude_dirs:
|
|
294
|
+
reload_paths = tuple(
|
|
295
|
+
path
|
|
296
|
+
for path in reload_paths
|
|
297
|
+
if all(not path.samefile(exclude) for exclude in exclude_dirs)
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
console.debug(f"Reload paths: {list(map(str, reload_paths))}")
|
|
301
|
+
|
|
302
|
+
return reload_paths
|
|
268
303
|
|
|
269
304
|
|
|
270
305
|
def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
|
|
@@ -283,7 +318,7 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
283
318
|
port=port,
|
|
284
319
|
log_level=loglevel.value,
|
|
285
320
|
reload=True,
|
|
286
|
-
reload_dirs=list(map(str,
|
|
321
|
+
reload_dirs=list(map(str, get_reload_paths())),
|
|
287
322
|
)
|
|
288
323
|
|
|
289
324
|
|
|
@@ -310,8 +345,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
310
345
|
interface=Interfaces.ASGI,
|
|
311
346
|
log_level=LogLevels(loglevel.value),
|
|
312
347
|
reload=True,
|
|
313
|
-
reload_paths=
|
|
314
|
-
reload_ignore_dirs=[".web", ".states"],
|
|
348
|
+
reload_paths=get_reload_paths(),
|
|
315
349
|
).serve()
|
|
316
350
|
except ImportError:
|
|
317
351
|
console.error(
|
|
@@ -368,34 +402,49 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
|
|
|
368
402
|
|
|
369
403
|
app_module = get_app_module()
|
|
370
404
|
|
|
371
|
-
run_backend_prod = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
|
|
372
|
-
run_backend_prod_windows = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
|
|
373
405
|
command = (
|
|
374
406
|
[
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
407
|
+
"uvicorn",
|
|
408
|
+
*(
|
|
409
|
+
[
|
|
410
|
+
"--limit-max-requests",
|
|
411
|
+
str(config.gunicorn_max_requests),
|
|
412
|
+
]
|
|
413
|
+
if config.gunicorn_max_requests > 0
|
|
414
|
+
else []
|
|
415
|
+
),
|
|
416
|
+
*("--timeout-keep-alive", str(config.timeout)),
|
|
417
|
+
*("--host", host),
|
|
418
|
+
*("--port", str(port)),
|
|
419
|
+
*("--workers", str(_get_backend_workers())),
|
|
380
420
|
app_module,
|
|
381
421
|
]
|
|
382
422
|
if constants.IS_WINDOWS
|
|
383
423
|
else [
|
|
384
|
-
|
|
385
|
-
"--
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
424
|
+
"gunicorn",
|
|
425
|
+
*("--worker-class", config.gunicorn_worker_class),
|
|
426
|
+
*(
|
|
427
|
+
[
|
|
428
|
+
"--max-requests",
|
|
429
|
+
str(config.gunicorn_max_requests),
|
|
430
|
+
"--max-requests-jitter",
|
|
431
|
+
str(config.gunicorn_max_requests_jitter),
|
|
432
|
+
]
|
|
433
|
+
if config.gunicorn_max_requests > 0
|
|
434
|
+
else []
|
|
435
|
+
),
|
|
436
|
+
"--preload",
|
|
437
|
+
*("--timeout", str(config.timeout)),
|
|
438
|
+
*("--bind", f"{host}:{port}"),
|
|
439
|
+
*("--threads", str(_get_backend_workers())),
|
|
389
440
|
f"{app_module}()",
|
|
390
441
|
]
|
|
391
442
|
)
|
|
392
443
|
|
|
393
444
|
command += [
|
|
394
|
-
"--log-level",
|
|
395
|
-
loglevel.value,
|
|
396
|
-
"--workers",
|
|
397
|
-
str(_get_backend_workers()),
|
|
445
|
+
*("--log-level", loglevel.value),
|
|
398
446
|
]
|
|
447
|
+
|
|
399
448
|
processes.new_process(
|
|
400
449
|
command,
|
|
401
450
|
run=True,
|
|
@@ -535,3 +584,12 @@ def is_prod_mode() -> bool:
|
|
|
535
584
|
"""
|
|
536
585
|
current_mode = environment.REFLEX_ENV_MODE.get()
|
|
537
586
|
return current_mode == constants.Env.PROD
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def get_compile_context() -> constants.CompileContext:
|
|
590
|
+
"""Check if the app is compiled for deploy.
|
|
591
|
+
|
|
592
|
+
Returns:
|
|
593
|
+
Whether the app is being compiled for deploy.
|
|
594
|
+
"""
|
|
595
|
+
return environment.REFLEX_COMPILE_CONTEXT.get()
|
reflex/utils/format.py
CHANGED
|
@@ -27,6 +27,36 @@ WRAP_MAP = {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def length_of_largest_common_substring(str1: str, str2: str) -> int:
|
|
31
|
+
"""Find the length of the largest common substring between two strings.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
str1: The first string.
|
|
35
|
+
str2: The second string.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
The length of the largest common substring.
|
|
39
|
+
"""
|
|
40
|
+
if not str1 or not str2:
|
|
41
|
+
return 0
|
|
42
|
+
|
|
43
|
+
# Create a matrix of size (len(str1) + 1) x (len(str2) + 1)
|
|
44
|
+
dp = [[0] * (len(str2) + 1) for _ in range(len(str1) + 1)]
|
|
45
|
+
|
|
46
|
+
# Variables to keep track of maximum length and ending position
|
|
47
|
+
max_length = 0
|
|
48
|
+
|
|
49
|
+
# Fill the dp matrix
|
|
50
|
+
for i in range(1, len(str1) + 1):
|
|
51
|
+
for j in range(1, len(str2) + 1):
|
|
52
|
+
if str1[i - 1] == str2[j - 1]:
|
|
53
|
+
dp[i][j] = dp[i - 1][j - 1] + 1
|
|
54
|
+
if dp[i][j] > max_length:
|
|
55
|
+
max_length = dp[i][j]
|
|
56
|
+
|
|
57
|
+
return max_length
|
|
58
|
+
|
|
59
|
+
|
|
30
60
|
def get_close_char(open: str, close: str | None = None) -> str:
|
|
31
61
|
"""Check if the given character is a valid brace.
|
|
32
62
|
|
|
@@ -138,7 +168,7 @@ def to_snake_case(text: str) -> str:
|
|
|
138
168
|
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower().replace("-", "_")
|
|
139
169
|
|
|
140
170
|
|
|
141
|
-
def to_camel_case(text: str,
|
|
171
|
+
def to_camel_case(text: str, treat_hyphens_as_underscores: bool = True) -> str:
|
|
142
172
|
"""Convert a string to camel case.
|
|
143
173
|
|
|
144
174
|
The first word in the text is converted to lowercase and
|
|
@@ -146,17 +176,16 @@ def to_camel_case(text: str, allow_hyphens: bool = False) -> str:
|
|
|
146
176
|
|
|
147
177
|
Args:
|
|
148
178
|
text: The string to convert.
|
|
149
|
-
|
|
179
|
+
treat_hyphens_as_underscores: Whether to allow hyphens in the string.
|
|
150
180
|
|
|
151
181
|
Returns:
|
|
152
182
|
The camel case string.
|
|
153
183
|
"""
|
|
154
|
-
char = "_" if
|
|
155
|
-
words = re.split(f"[{char}]", text
|
|
156
|
-
leading_underscores_or_hyphens = "".join(re.findall(rf"^[{char}]+", text))
|
|
184
|
+
char = "_" if not treat_hyphens_as_underscores else "-_"
|
|
185
|
+
words = re.split(f"[{char}]", text)
|
|
157
186
|
# Capitalize the first letter of each word except the first one
|
|
158
187
|
converted_word = words[0] + "".join(x.capitalize() for x in words[1:])
|
|
159
|
-
return
|
|
188
|
+
return converted_word
|
|
160
189
|
|
|
161
190
|
|
|
162
191
|
def to_title_case(text: str, sep: str = "") -> str:
|
reflex/utils/path_ops.py
CHANGED
|
@@ -6,6 +6,7 @@ import json
|
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
8
|
import shutil
|
|
9
|
+
import stat
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
|
|
11
12
|
from reflex import constants
|
|
@@ -15,6 +16,19 @@ from reflex.config import environment, get_config
|
|
|
15
16
|
join = os.linesep.join
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
def chmod_rm(path: Path):
|
|
20
|
+
"""Remove a file or directory with chmod.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
path: The path to the file or directory.
|
|
24
|
+
"""
|
|
25
|
+
path.chmod(stat.S_IWRITE)
|
|
26
|
+
if path.is_dir():
|
|
27
|
+
shutil.rmtree(path)
|
|
28
|
+
elif path.is_file():
|
|
29
|
+
path.unlink()
|
|
30
|
+
|
|
31
|
+
|
|
18
32
|
def rm(path: str | Path):
|
|
19
33
|
"""Remove a file or directory.
|
|
20
34
|
|
|
@@ -23,7 +37,8 @@ def rm(path: str | Path):
|
|
|
23
37
|
"""
|
|
24
38
|
path = Path(path)
|
|
25
39
|
if path.is_dir():
|
|
26
|
-
|
|
40
|
+
# In Python 3.12, onerror is deprecated in favor of onexc
|
|
41
|
+
shutil.rmtree(path, onerror=lambda _func, _path, _info: chmod_rm(path))
|
|
27
42
|
elif path.is_file():
|
|
28
43
|
path.unlink()
|
|
29
44
|
|
|
@@ -247,6 +262,22 @@ def find_replace(directory: str | Path, find: str, replace: str):
|
|
|
247
262
|
filepath.write_text(text, encoding="utf-8")
|
|
248
263
|
|
|
249
264
|
|
|
265
|
+
def samefile(file1: Path, file2: Path) -> bool:
|
|
266
|
+
"""Check if two files are the same.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
file1: The first file.
|
|
270
|
+
file2: The second file.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Whether the files are the same. If either file does not exist, returns False.
|
|
274
|
+
"""
|
|
275
|
+
if file1.exists() and file2.exists():
|
|
276
|
+
return file1.samefile(file2)
|
|
277
|
+
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
|
|
250
281
|
def update_directory_tree(src: Path, dest: Path):
|
|
251
282
|
"""Recursively copies a directory tree from src to dest.
|
|
252
283
|
Only copies files if the destination file is missing or modified earlier than the source file.
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -23,7 +23,8 @@ import zipfile
|
|
|
23
23
|
from datetime import datetime
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
from types import ModuleType
|
|
26
|
-
from typing import
|
|
26
|
+
from typing import Callable, List, NamedTuple, Optional
|
|
27
|
+
from urllib.parse import urlparse
|
|
27
28
|
|
|
28
29
|
import httpx
|
|
29
30
|
import typer
|
|
@@ -98,6 +99,15 @@ def get_states_dir() -> Path:
|
|
|
98
99
|
return environment.REFLEX_STATES_WORKDIR.get()
|
|
99
100
|
|
|
100
101
|
|
|
102
|
+
def get_backend_dir() -> Path:
|
|
103
|
+
"""Get the working directory for the backend.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The working directory.
|
|
107
|
+
"""
|
|
108
|
+
return get_web_dir() / constants.Dirs.BACKEND
|
|
109
|
+
|
|
110
|
+
|
|
101
111
|
def check_latest_package_version(package_name: str):
|
|
102
112
|
"""Check if the latest version of the package is installed.
|
|
103
113
|
|
|
@@ -836,6 +846,7 @@ def _compile_package_json():
|
|
|
836
846
|
},
|
|
837
847
|
dependencies=constants.PackageJson.DEPENDENCIES,
|
|
838
848
|
dev_dependencies=constants.PackageJson.DEV_DEPENDENCIES,
|
|
849
|
+
overrides=constants.PackageJson.OVERRIDES,
|
|
839
850
|
)
|
|
840
851
|
|
|
841
852
|
|
|
@@ -1225,6 +1236,21 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1225
1236
|
)
|
|
1226
1237
|
|
|
1227
1238
|
|
|
1239
|
+
def check_running_mode(frontend: bool, backend: bool) -> tuple[bool, bool]:
|
|
1240
|
+
"""Check if the app is running in frontend or backend mode.
|
|
1241
|
+
|
|
1242
|
+
Args:
|
|
1243
|
+
frontend: Whether to run the frontend of the app.
|
|
1244
|
+
backend: Whether to run the backend of the app.
|
|
1245
|
+
|
|
1246
|
+
Returns:
|
|
1247
|
+
The running modes.
|
|
1248
|
+
"""
|
|
1249
|
+
if not frontend and not backend:
|
|
1250
|
+
return True, True
|
|
1251
|
+
return frontend, backend
|
|
1252
|
+
|
|
1253
|
+
|
|
1228
1254
|
def needs_reinit(frontend: bool = True) -> bool:
|
|
1229
1255
|
"""Check if an app needs to be reinitialized.
|
|
1230
1256
|
|
|
@@ -1293,10 +1319,13 @@ def validate_bun():
|
|
|
1293
1319
|
"""
|
|
1294
1320
|
bun_path = path_ops.get_bun_path()
|
|
1295
1321
|
|
|
1296
|
-
if bun_path
|
|
1322
|
+
if bun_path is None:
|
|
1323
|
+
return
|
|
1324
|
+
|
|
1325
|
+
if not path_ops.samefile(bun_path, constants.Bun.DEFAULT_PATH):
|
|
1297
1326
|
console.info(f"Using custom Bun path: {bun_path}")
|
|
1298
1327
|
bun_version = get_bun_version()
|
|
1299
|
-
if
|
|
1328
|
+
if bun_version is None:
|
|
1300
1329
|
console.error(
|
|
1301
1330
|
"Failed to obtain bun version. Make sure the specified bun path in your config is correct."
|
|
1302
1331
|
)
|
|
@@ -1661,9 +1690,11 @@ def validate_and_create_app_using_remote_template(
|
|
|
1661
1690
|
|
|
1662
1691
|
template_url = templates[template].code_url
|
|
1663
1692
|
else:
|
|
1693
|
+
template_parsed_url = urlparse(template)
|
|
1664
1694
|
# Check if the template is a github repo.
|
|
1665
|
-
if
|
|
1666
|
-
|
|
1695
|
+
if template_parsed_url.hostname == "github.com":
|
|
1696
|
+
path = template_parsed_url.path.strip("/").removesuffix(".git")
|
|
1697
|
+
template_url = f"https://github.com/{path}/archive/main.zip"
|
|
1667
1698
|
else:
|
|
1668
1699
|
console.error(f"Template `{template}` not found or invalid.")
|
|
1669
1700
|
raise typer.Exit(1)
|
|
@@ -1980,38 +2011,17 @@ def is_generation_hash(template: str) -> bool:
|
|
|
1980
2011
|
return re.match(r"^[0-9a-f]{32,}$", template) is not None
|
|
1981
2012
|
|
|
1982
2013
|
|
|
1983
|
-
def
|
|
1984
|
-
|
|
1985
|
-
allowed_tiers: list[str],
|
|
1986
|
-
fallback_value: Any,
|
|
1987
|
-
help_link: str | None = None,
|
|
1988
|
-
):
|
|
1989
|
-
"""Check if a config option is allowed for the authenticated user's current tier.
|
|
2014
|
+
def get_user_tier():
|
|
2015
|
+
"""Get the current user's tier.
|
|
1990
2016
|
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
allowed_tiers: The tiers that are allowed to use the option.
|
|
1994
|
-
fallback_value: The fallback value if the option is not allowed.
|
|
1995
|
-
help_link: The help link to show to a user that is authenticated.
|
|
2017
|
+
Returns:
|
|
2018
|
+
The current user's tier.
|
|
1996
2019
|
"""
|
|
1997
2020
|
from reflex_cli.v2.utils import hosting
|
|
1998
2021
|
|
|
1999
|
-
config = get_config()
|
|
2000
2022
|
authenticated_token = hosting.authenticated_token()
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
else:
|
|
2007
|
-
current_tier = authenticated_token[1].get("tier", "").lower()
|
|
2008
|
-
the_remedy = (
|
|
2009
|
-
f"Your current subscription tier is `{current_tier}`. "
|
|
2010
|
-
f"Please upgrade to {allowed_tiers} to access this option. "
|
|
2011
|
-
)
|
|
2012
|
-
if help_link:
|
|
2013
|
-
the_remedy += f"See {help_link} for more information."
|
|
2014
|
-
if current_tier not in allowed_tiers:
|
|
2015
|
-
console.warn(f"Config option `{option_name}` is restricted. {the_remedy}")
|
|
2016
|
-
setattr(config, option_name, fallback_value)
|
|
2017
|
-
config._set_persistent(**{option_name: fallback_value})
|
|
2023
|
+
return (
|
|
2024
|
+
authenticated_token[1].get("tier", "").lower()
|
|
2025
|
+
if authenticated_token[0]
|
|
2026
|
+
else "anonymous"
|
|
2027
|
+
)
|
reflex/utils/processes.py
CHANGED
|
@@ -116,17 +116,14 @@ def change_port(port: int, _type: str) -> int:
|
|
|
116
116
|
return new_port
|
|
117
117
|
|
|
118
118
|
|
|
119
|
-
def handle_port(service_name: str, port: int,
|
|
119
|
+
def handle_port(service_name: str, port: int, auto_increment: bool) -> int:
|
|
120
120
|
"""Change port if the specified port is in use and is not explicitly specified as a CLI arg or config arg.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
We make an assumption that when port is the default port,then it hasn't been explicitly set since its not straightforward
|
|
124
|
-
to know whether a port was explicitly provided by the user unless its any other than the default.
|
|
121
|
+
Otherwise tell the user the port is in use and exit the app.
|
|
125
122
|
|
|
126
123
|
Args:
|
|
127
124
|
service_name: The frontend or backend.
|
|
128
125
|
port: The provided port.
|
|
129
|
-
|
|
126
|
+
auto_increment: Whether to automatically increment the port.
|
|
130
127
|
|
|
131
128
|
Returns:
|
|
132
129
|
The port to run the service on.
|
|
@@ -134,13 +131,15 @@ def handle_port(service_name: str, port: int, default_port: int) -> int:
|
|
|
134
131
|
Raises:
|
|
135
132
|
Exit:when the port is in use.
|
|
136
133
|
"""
|
|
137
|
-
if
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
134
|
+
if (process := get_process_on_port(port)) is None:
|
|
135
|
+
return port
|
|
136
|
+
if auto_increment:
|
|
137
|
+
return change_port(port, service_name)
|
|
138
|
+
else:
|
|
139
|
+
console.error(
|
|
140
|
+
f"{service_name.capitalize()} port: {port} is already in use by PID: {process.pid}."
|
|
141
|
+
)
|
|
142
|
+
raise typer.Exit()
|
|
144
143
|
|
|
145
144
|
|
|
146
145
|
def new_process(
|
reflex/utils/serializers.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import contextlib
|
|
5
6
|
import dataclasses
|
|
6
7
|
import functools
|
|
7
8
|
import json
|
|
@@ -24,6 +25,9 @@ from typing import (
|
|
|
24
25
|
overload,
|
|
25
26
|
)
|
|
26
27
|
|
|
28
|
+
from pydantic import BaseModel as BaseModelV2
|
|
29
|
+
from pydantic.v1 import BaseModel as BaseModelV1
|
|
30
|
+
|
|
27
31
|
from reflex.base import Base
|
|
28
32
|
from reflex.constants.colors import Color, format_color
|
|
29
33
|
from reflex.utils import types
|
|
@@ -270,43 +274,24 @@ def serialize_base(value: Base) -> dict:
|
|
|
270
274
|
}
|
|
271
275
|
|
|
272
276
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
@serializer(to=dict)
|
|
277
|
-
def serialize_base_model_v1(model: BaseModelV1) -> dict:
|
|
278
|
-
"""Serialize a pydantic v1 BaseModel instance.
|
|
279
|
-
|
|
280
|
-
Args:
|
|
281
|
-
model: The BaseModel to serialize.
|
|
282
|
-
|
|
283
|
-
Returns:
|
|
284
|
-
The serialized BaseModel.
|
|
285
|
-
"""
|
|
286
|
-
return model.dict()
|
|
287
|
-
|
|
288
|
-
from pydantic import BaseModel as BaseModelV2
|
|
277
|
+
@serializer(to=dict)
|
|
278
|
+
def serialize_base_model_v1(model: BaseModelV1) -> dict:
|
|
279
|
+
"""Serialize a pydantic v1 BaseModel instance.
|
|
289
280
|
|
|
290
|
-
|
|
281
|
+
Args:
|
|
282
|
+
model: The BaseModel to serialize.
|
|
291
283
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
284
|
+
Returns:
|
|
285
|
+
The serialized BaseModel.
|
|
286
|
+
"""
|
|
287
|
+
return model.dict()
|
|
295
288
|
|
|
296
|
-
Args:
|
|
297
|
-
model: The BaseModel to serialize.
|
|
298
289
|
|
|
299
|
-
|
|
300
|
-
The serialized BaseModel.
|
|
301
|
-
"""
|
|
302
|
-
return model.model_dump()
|
|
303
|
-
except ImportError:
|
|
304
|
-
# Older pydantic v1 import
|
|
305
|
-
from pydantic import BaseModel as BaseModelV1
|
|
290
|
+
if BaseModelV1 is not BaseModelV2:
|
|
306
291
|
|
|
307
292
|
@serializer(to=dict)
|
|
308
|
-
def
|
|
309
|
-
"""Serialize a pydantic
|
|
293
|
+
def serialize_base_model_v2(model: BaseModelV2) -> dict:
|
|
294
|
+
"""Serialize a pydantic v2 BaseModel instance.
|
|
310
295
|
|
|
311
296
|
Args:
|
|
312
297
|
model: The BaseModel to serialize.
|
|
@@ -314,7 +299,7 @@ except ImportError:
|
|
|
314
299
|
Returns:
|
|
315
300
|
The serialized BaseModel.
|
|
316
301
|
"""
|
|
317
|
-
return model.
|
|
302
|
+
return model.model_dump()
|
|
318
303
|
|
|
319
304
|
|
|
320
305
|
@serializer
|
|
@@ -382,7 +367,7 @@ def serialize_color(color: Color) -> str:
|
|
|
382
367
|
return format_color(color.color, color.shade, color.alpha)
|
|
383
368
|
|
|
384
369
|
|
|
385
|
-
|
|
370
|
+
with contextlib.suppress(ImportError):
|
|
386
371
|
from pandas import DataFrame
|
|
387
372
|
|
|
388
373
|
def format_dataframe_values(df: DataFrame) -> List[List[Any]]:
|
|
@@ -414,10 +399,8 @@ try:
|
|
|
414
399
|
"data": format_dataframe_values(df),
|
|
415
400
|
}
|
|
416
401
|
|
|
417
|
-
except ImportError:
|
|
418
|
-
pass
|
|
419
402
|
|
|
420
|
-
|
|
403
|
+
with contextlib.suppress(ImportError):
|
|
421
404
|
from plotly.graph_objects import Figure, layout
|
|
422
405
|
from plotly.io import to_json
|
|
423
406
|
|
|
@@ -448,11 +431,8 @@ try:
|
|
|
448
431
|
"layout": json.loads(str(to_json(template.layout))),
|
|
449
432
|
}
|
|
450
433
|
|
|
451
|
-
except ImportError:
|
|
452
|
-
pass
|
|
453
434
|
|
|
454
|
-
|
|
455
|
-
try:
|
|
435
|
+
with contextlib.suppress(ImportError):
|
|
456
436
|
import base64
|
|
457
437
|
import io
|
|
458
438
|
|
|
@@ -489,6 +469,3 @@ try:
|
|
|
489
469
|
mime_type = "image/png"
|
|
490
470
|
|
|
491
471
|
return f"data:{mime_type};base64,{base64_image}"
|
|
492
|
-
|
|
493
|
-
except ImportError:
|
|
494
|
-
pass
|
reflex/utils/telemetry.py
CHANGED
|
@@ -8,23 +8,17 @@ import multiprocessing
|
|
|
8
8
|
import platform
|
|
9
9
|
import warnings
|
|
10
10
|
from contextlib import suppress
|
|
11
|
-
|
|
12
|
-
from reflex.config import environment
|
|
13
|
-
|
|
14
|
-
try:
|
|
15
|
-
from datetime import UTC, datetime
|
|
16
|
-
except ImportError:
|
|
17
|
-
from datetime import datetime
|
|
18
|
-
|
|
19
|
-
UTC = None
|
|
11
|
+
from datetime import datetime, timezone
|
|
20
12
|
|
|
21
13
|
import httpx
|
|
22
14
|
import psutil
|
|
23
15
|
|
|
24
16
|
from reflex import constants
|
|
17
|
+
from reflex.config import environment
|
|
25
18
|
from reflex.utils import console
|
|
26
19
|
from reflex.utils.prerequisites import ensure_reflex_installation_id, get_project_hash
|
|
27
20
|
|
|
21
|
+
UTC = timezone.utc
|
|
28
22
|
POSTHOG_API_URL: str = "https://app.posthog.com/capture/"
|
|
29
23
|
|
|
30
24
|
|
|
@@ -121,12 +115,7 @@ def _prepare_event(event: str, **kwargs) -> dict:
|
|
|
121
115
|
)
|
|
122
116
|
return {}
|
|
123
117
|
|
|
124
|
-
|
|
125
|
-
# for python 3.10
|
|
126
|
-
stamp = datetime.utcnow().isoformat()
|
|
127
|
-
else:
|
|
128
|
-
# for python 3.11 & 3.12
|
|
129
|
-
stamp = datetime.now(UTC).isoformat()
|
|
118
|
+
stamp = datetime.now(UTC).isoformat()
|
|
130
119
|
|
|
131
120
|
cpuinfo = get_cpu_info()
|
|
132
121
|
|