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.

Files changed (128) hide show
  1. reflex/.templates/jinja/web/package.json.jinja2 +7 -1
  2. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -1
  3. reflex/__init__.py +1 -0
  4. reflex/__init__.pyi +1 -0
  5. reflex/app.py +269 -86
  6. reflex/base.py +4 -10
  7. reflex/compiler/compiler.py +46 -12
  8. reflex/compiler/templates.py +1 -2
  9. reflex/compiler/utils.py +23 -14
  10. reflex/components/base/bare.py +109 -16
  11. reflex/components/component.py +179 -124
  12. reflex/components/core/__init__.py +1 -0
  13. reflex/components/core/__init__.pyi +1 -0
  14. reflex/components/core/auto_scroll.py +114 -0
  15. reflex/components/core/auto_scroll.pyi +284 -0
  16. reflex/components/core/banner.py +40 -9
  17. reflex/components/core/banner.pyi +400 -87
  18. reflex/components/core/breakpoints.py +1 -1
  19. reflex/components/core/cond.py +0 -8
  20. reflex/components/core/foreach.py +12 -2
  21. reflex/components/core/html.pyi +200 -19
  22. reflex/components/core/match.py +4 -4
  23. reflex/components/core/sticky.py +4 -30
  24. reflex/components/core/sticky.pyi +874 -90
  25. reflex/components/core/upload.py +3 -5
  26. reflex/components/core/upload.pyi +2 -4
  27. reflex/components/datadisplay/code.py +36 -10
  28. reflex/components/datadisplay/code.pyi +1 -1
  29. reflex/components/datadisplay/dataeditor.py +1 -3
  30. reflex/components/datadisplay/dataeditor.pyi +1 -3
  31. reflex/components/el/elements/base.py +95 -17
  32. reflex/components/el/elements/base.pyi +278 -19
  33. reflex/components/el/elements/forms.py +124 -102
  34. reflex/components/el/elements/forms.pyi +2787 -365
  35. reflex/components/el/elements/inline.py +24 -15
  36. reflex/components/el/elements/inline.pyi +5655 -546
  37. reflex/components/el/elements/media.py +79 -95
  38. reflex/components/el/elements/media.pyi +5167 -565
  39. reflex/components/el/elements/metadata.py +19 -17
  40. reflex/components/el/elements/metadata.pyi +841 -89
  41. reflex/components/el/elements/other.py +3 -5
  42. reflex/components/el/elements/other.pyi +1404 -137
  43. reflex/components/el/elements/scripts.py +10 -13
  44. reflex/components/el/elements/scripts.pyi +634 -65
  45. reflex/components/el/elements/sectioning.pyi +3001 -286
  46. reflex/components/el/elements/tables.py +14 -35
  47. reflex/components/el/elements/tables.pyi +2029 -218
  48. reflex/components/el/elements/typography.py +10 -13
  49. reflex/components/el/elements/typography.pyi +3014 -297
  50. reflex/components/lucide/icon.py +22 -6
  51. reflex/components/markdown/markdown.py +30 -10
  52. reflex/components/markdown/markdown.pyi +3 -2
  53. reflex/components/plotly/plotly.py +1 -3
  54. reflex/components/plotly/plotly.pyi +1 -3
  55. reflex/components/radix/primitives/form.pyi +624 -93
  56. reflex/components/radix/themes/color_mode.py +1 -1
  57. reflex/components/radix/themes/color_mode.pyi +213 -31
  58. reflex/components/radix/themes/components/alert_dialog.pyi +199 -18
  59. reflex/components/radix/themes/components/badge.pyi +199 -18
  60. reflex/components/radix/themes/components/button.pyi +213 -31
  61. reflex/components/radix/themes/components/callout.pyi +1000 -95
  62. reflex/components/radix/themes/components/card.pyi +199 -18
  63. reflex/components/radix/themes/components/context_menu.py +79 -1
  64. reflex/components/radix/themes/components/context_menu.pyi +320 -1
  65. reflex/components/radix/themes/components/dialog.pyi +199 -18
  66. reflex/components/radix/themes/components/hover_card.pyi +199 -18
  67. reflex/components/radix/themes/components/icon_button.pyi +213 -31
  68. reflex/components/radix/themes/components/inset.pyi +199 -18
  69. reflex/components/radix/themes/components/popover.pyi +199 -18
  70. reflex/components/radix/themes/components/table.pyi +1437 -154
  71. reflex/components/radix/themes/components/text_area.py +2 -2
  72. reflex/components/radix/themes/components/text_area.pyi +201 -20
  73. reflex/components/radix/themes/components/text_field.py +1 -1
  74. reflex/components/radix/themes/components/text_field.pyi +444 -88
  75. reflex/components/radix/themes/layout/box.pyi +200 -19
  76. reflex/components/radix/themes/layout/center.pyi +199 -18
  77. reflex/components/radix/themes/layout/container.pyi +199 -18
  78. reflex/components/radix/themes/layout/flex.pyi +199 -18
  79. reflex/components/radix/themes/layout/grid.pyi +199 -18
  80. reflex/components/radix/themes/layout/list.pyi +604 -57
  81. reflex/components/radix/themes/layout/section.pyi +199 -18
  82. reflex/components/radix/themes/layout/spacer.pyi +199 -18
  83. reflex/components/radix/themes/layout/stack.pyi +597 -54
  84. reflex/components/radix/themes/typography/blockquote.pyi +200 -19
  85. reflex/components/radix/themes/typography/code.pyi +199 -18
  86. reflex/components/radix/themes/typography/heading.pyi +199 -18
  87. reflex/components/radix/themes/typography/link.pyi +238 -28
  88. reflex/components/radix/themes/typography/text.pyi +1394 -127
  89. reflex/components/react_player/react_player.py +1 -1
  90. reflex/components/react_player/react_player.pyi +1 -3
  91. reflex/components/sonner/toast.py +41 -12
  92. reflex/components/sonner/toast.pyi +20 -6
  93. reflex/components/tags/iter_tag.py +4 -0
  94. reflex/components/tags/tag.py +3 -3
  95. reflex/config.py +187 -28
  96. reflex/constants/__init__.py +2 -0
  97. reflex/constants/base.py +6 -0
  98. reflex/constants/compiler.py +9 -0
  99. reflex/constants/event.py +1 -0
  100. reflex/constants/installer.py +8 -5
  101. reflex/constants/utils.py +1 -3
  102. reflex/event.py +7 -16
  103. reflex/experimental/layout.pyi +597 -54
  104. reflex/py.typed +0 -0
  105. reflex/reflex.py +44 -48
  106. reflex/state.py +49 -44
  107. reflex/style.py +15 -22
  108. reflex/testing.py +2 -0
  109. reflex/utils/build.py +12 -0
  110. reflex/utils/console.py +4 -0
  111. reflex/utils/decorator.py +25 -0
  112. reflex/utils/exec.py +92 -34
  113. reflex/utils/format.py +35 -6
  114. reflex/utils/path_ops.py +32 -1
  115. reflex/utils/prerequisites.py +45 -35
  116. reflex/utils/processes.py +12 -13
  117. reflex/utils/serializers.py +20 -43
  118. reflex/utils/telemetry.py +4 -15
  119. reflex/utils/types.py +36 -66
  120. reflex/vars/base.py +53 -76
  121. reflex/vars/function.py +17 -5
  122. reflex/vars/number.py +1 -1
  123. reflex/vars/sequence.py +80 -4
  124. {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/METADATA +4 -5
  125. {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/RECORD +128 -124
  126. {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/LICENSE +0 -0
  127. {reflex-0.7.0a4.dist-info → reflex-0.7.1.dist-info}/WHEEL +0 -0
  128. {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 get_reload_dirs() -> list[Path]:
246
- """Get the reload directories for the backend.
246
+ def get_reload_paths() -> Sequence[Path]:
247
+ """Get the reload paths for the backend.
247
248
 
248
249
  Returns:
249
- The reload directories for the backend.
250
+ The reload paths for the backend.
250
251
  """
251
252
  config = get_config()
252
- reload_dirs = [Path(config.app_name)]
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
- if 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
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
- reload_dirs = [module_path]
267
- return reload_dirs
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, get_reload_dirs())),
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=get_reload_dirs(),
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
- *run_backend_prod_windows,
376
- "--host",
377
- host,
378
- "--port",
379
- str(port),
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
- *run_backend_prod,
385
- "--bind",
386
- f"{host}:{port}",
387
- "--threads",
388
- str(_get_backend_workers()),
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, allow_hyphens: bool = False) -> 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
- allow_hyphens: Whether to allow hyphens in the string.
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 allow_hyphens else "-_"
155
- words = re.split(f"[{char}]", text.lstrip(char))
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 leading_underscores_or_hyphens + converted_word
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
- shutil.rmtree(path)
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.
@@ -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 Any, Callable, List, NamedTuple, Optional
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 and not bun_path.samefile(constants.Bun.DEFAULT_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 not bun_version:
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 template.startswith("https://github.com"):
1666
- template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip"
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 check_config_option_in_tier(
1984
- option_name: str,
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
- Args:
1992
- option_name: The name of the option to check.
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
- if not authenticated_token[0]:
2002
- the_remedy = (
2003
- "You are currently logged out. Run `reflex login` to access this option."
2004
- )
2005
- current_tier = "anonymous"
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, default_port: int) -> 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
- otherwise tell the user the port is in use and exit the app.
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
- default_port: The default port number associated with the specified service.
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 is_process_on_port(port):
138
- if port == int(default_port):
139
- return change_port(port, service_name)
140
- else:
141
- console.error(f"{service_name.capitalize()} port: {port} is already in use")
142
- raise typer.Exit()
143
- return port
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(
@@ -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
- try:
274
- from pydantic.v1 import BaseModel as BaseModelV1
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
- if BaseModelV1 is not BaseModelV2:
281
+ Args:
282
+ model: The BaseModel to serialize.
291
283
 
292
- @serializer(to=dict)
293
- def serialize_base_model_v2(model: BaseModelV2) -> dict:
294
- """Serialize a pydantic v2 BaseModel instance.
284
+ Returns:
285
+ The serialized BaseModel.
286
+ """
287
+ return model.dict()
295
288
 
296
- Args:
297
- model: The BaseModel to serialize.
298
289
 
299
- Returns:
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 serialize_base_model_v1(model: BaseModelV1) -> dict:
309
- """Serialize a pydantic v1 BaseModel instance.
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.dict()
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
- try:
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
- try:
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
- if UTC is None:
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