reflex 0.7.0a5__py3-none-any.whl → 0.7.1a2__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 (126) hide show
  1. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -1
  2. reflex/__init__.py +1 -0
  3. reflex/__init__.pyi +1 -0
  4. reflex/app.py +255 -81
  5. reflex/base.py +4 -10
  6. reflex/compiler/compiler.py +46 -12
  7. reflex/compiler/templates.py +1 -2
  8. reflex/compiler/utils.py +23 -14
  9. reflex/components/base/bare.py +109 -16
  10. reflex/components/component.py +179 -124
  11. reflex/components/core/__init__.py +1 -0
  12. reflex/components/core/__init__.pyi +1 -0
  13. reflex/components/core/auto_scroll.py +111 -0
  14. reflex/components/core/auto_scroll.pyi +284 -0
  15. reflex/components/core/banner.py +40 -9
  16. reflex/components/core/banner.pyi +400 -87
  17. reflex/components/core/breakpoints.py +1 -1
  18. reflex/components/core/cond.py +0 -8
  19. reflex/components/core/foreach.py +12 -2
  20. reflex/components/core/html.pyi +200 -19
  21. reflex/components/core/match.py +4 -4
  22. reflex/components/core/sticky.pyi +874 -90
  23. reflex/components/core/upload.py +3 -5
  24. reflex/components/core/upload.pyi +2 -4
  25. reflex/components/datadisplay/code.py +36 -10
  26. reflex/components/datadisplay/code.pyi +1 -1
  27. reflex/components/datadisplay/dataeditor.py +1 -3
  28. reflex/components/datadisplay/dataeditor.pyi +1 -3
  29. reflex/components/el/elements/base.py +95 -17
  30. reflex/components/el/elements/base.pyi +278 -19
  31. reflex/components/el/elements/forms.py +124 -102
  32. reflex/components/el/elements/forms.pyi +2787 -365
  33. reflex/components/el/elements/inline.py +24 -15
  34. reflex/components/el/elements/inline.pyi +5655 -546
  35. reflex/components/el/elements/media.py +79 -95
  36. reflex/components/el/elements/media.pyi +5167 -565
  37. reflex/components/el/elements/metadata.py +19 -17
  38. reflex/components/el/elements/metadata.pyi +841 -89
  39. reflex/components/el/elements/other.py +3 -5
  40. reflex/components/el/elements/other.pyi +1404 -137
  41. reflex/components/el/elements/scripts.py +10 -13
  42. reflex/components/el/elements/scripts.pyi +634 -65
  43. reflex/components/el/elements/sectioning.pyi +3001 -286
  44. reflex/components/el/elements/tables.py +14 -35
  45. reflex/components/el/elements/tables.pyi +2029 -218
  46. reflex/components/el/elements/typography.py +10 -13
  47. reflex/components/el/elements/typography.pyi +3014 -297
  48. reflex/components/lucide/icon.py +22 -6
  49. reflex/components/markdown/markdown.py +30 -10
  50. reflex/components/markdown/markdown.pyi +3 -2
  51. reflex/components/plotly/plotly.py +1 -3
  52. reflex/components/plotly/plotly.pyi +1 -3
  53. reflex/components/radix/primitives/form.pyi +624 -93
  54. reflex/components/radix/themes/color_mode.py +1 -1
  55. reflex/components/radix/themes/color_mode.pyi +213 -31
  56. reflex/components/radix/themes/components/alert_dialog.pyi +199 -18
  57. reflex/components/radix/themes/components/badge.pyi +199 -18
  58. reflex/components/radix/themes/components/button.pyi +213 -31
  59. reflex/components/radix/themes/components/callout.pyi +1000 -95
  60. reflex/components/radix/themes/components/card.pyi +199 -18
  61. reflex/components/radix/themes/components/context_menu.py +79 -1
  62. reflex/components/radix/themes/components/context_menu.pyi +320 -1
  63. reflex/components/radix/themes/components/dialog.pyi +199 -18
  64. reflex/components/radix/themes/components/hover_card.pyi +199 -18
  65. reflex/components/radix/themes/components/icon_button.pyi +213 -31
  66. reflex/components/radix/themes/components/inset.pyi +199 -18
  67. reflex/components/radix/themes/components/popover.pyi +199 -18
  68. reflex/components/radix/themes/components/table.pyi +1437 -154
  69. reflex/components/radix/themes/components/text_area.py +2 -2
  70. reflex/components/radix/themes/components/text_area.pyi +201 -20
  71. reflex/components/radix/themes/components/text_field.py +1 -1
  72. reflex/components/radix/themes/components/text_field.pyi +444 -88
  73. reflex/components/radix/themes/layout/box.pyi +200 -19
  74. reflex/components/radix/themes/layout/center.pyi +199 -18
  75. reflex/components/radix/themes/layout/container.pyi +199 -18
  76. reflex/components/radix/themes/layout/flex.pyi +199 -18
  77. reflex/components/radix/themes/layout/grid.pyi +199 -18
  78. reflex/components/radix/themes/layout/list.pyi +604 -57
  79. reflex/components/radix/themes/layout/section.pyi +199 -18
  80. reflex/components/radix/themes/layout/spacer.pyi +199 -18
  81. reflex/components/radix/themes/layout/stack.pyi +597 -54
  82. reflex/components/radix/themes/typography/blockquote.pyi +200 -19
  83. reflex/components/radix/themes/typography/code.pyi +199 -18
  84. reflex/components/radix/themes/typography/heading.pyi +199 -18
  85. reflex/components/radix/themes/typography/link.pyi +238 -28
  86. reflex/components/radix/themes/typography/text.pyi +1394 -127
  87. reflex/components/react_player/react_player.py +1 -1
  88. reflex/components/react_player/react_player.pyi +1 -3
  89. reflex/components/sonner/toast.py +41 -12
  90. reflex/components/sonner/toast.pyi +20 -6
  91. reflex/components/tags/iter_tag.py +4 -0
  92. reflex/components/tags/tag.py +3 -3
  93. reflex/config.py +187 -28
  94. reflex/constants/__init__.py +2 -0
  95. reflex/constants/base.py +6 -0
  96. reflex/constants/compiler.py +9 -0
  97. reflex/constants/event.py +1 -0
  98. reflex/constants/installer.py +4 -5
  99. reflex/constants/utils.py +1 -3
  100. reflex/event.py +7 -16
  101. reflex/experimental/layout.pyi +597 -54
  102. reflex/py.typed +0 -0
  103. reflex/reflex.py +30 -41
  104. reflex/state.py +49 -44
  105. reflex/style.py +15 -22
  106. reflex/testing.py +2 -0
  107. reflex/utils/build.py +12 -0
  108. reflex/utils/console.py +4 -0
  109. reflex/utils/decorator.py +25 -0
  110. reflex/utils/exec.py +92 -34
  111. reflex/utils/format.py +35 -6
  112. reflex/utils/path_ops.py +16 -1
  113. reflex/utils/prerequisites.py +34 -8
  114. reflex/utils/processes.py +12 -13
  115. reflex/utils/serializers.py +20 -43
  116. reflex/utils/telemetry.py +4 -15
  117. reflex/utils/types.py +36 -66
  118. reflex/vars/base.py +53 -76
  119. reflex/vars/function.py +17 -5
  120. reflex/vars/number.py +1 -1
  121. reflex/vars/sequence.py +80 -4
  122. {reflex-0.7.0a5.dist-info → reflex-0.7.1a2.dist-info}/METADATA +4 -5
  123. {reflex-0.7.0a5.dist-info → reflex-0.7.1a2.dist-info}/RECORD +126 -122
  124. {reflex-0.7.0a5.dist-info → reflex-0.7.1a2.dist-info}/LICENSE +0 -0
  125. {reflex-0.7.0a5.dist-info → reflex-0.7.1a2.dist-info}/WHEEL +0 -0
  126. {reflex-0.7.0a5.dist-info → reflex-0.7.1a2.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
 
@@ -24,6 +24,7 @@ from datetime import datetime
24
24
  from pathlib import Path
25
25
  from types import ModuleType
26
26
  from typing import Any, 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
 
@@ -1679,9 +1689,11 @@ def validate_and_create_app_using_remote_template(
1679
1689
 
1680
1690
  template_url = templates[template].code_url
1681
1691
  else:
1692
+ template_parsed_url = urlparse(template)
1682
1693
  # Check if the template is a github repo.
1683
- if template.startswith("https://github.com"):
1684
- template_url = f"{template.strip('/').replace('.git', '')}/archive/main.zip"
1694
+ if template_parsed_url.hostname == "github.com":
1695
+ path = template_parsed_url.path.strip("/").removesuffix(".git")
1696
+ template_url = f"https://github.com/{path}/archive/main.zip"
1685
1697
  else:
1686
1698
  console.error(f"Template `{template}` not found or invalid.")
1687
1699
  raise typer.Exit(1)
@@ -1998,6 +2010,22 @@ def is_generation_hash(template: str) -> bool:
1998
2010
  return re.match(r"^[0-9a-f]{32,}$", template) is not None
1999
2011
 
2000
2012
 
2013
+ def get_user_tier():
2014
+ """Get the current user's tier.
2015
+
2016
+ Returns:
2017
+ The current user's tier.
2018
+ """
2019
+ from reflex_cli.v2.utils import hosting
2020
+
2021
+ authenticated_token = hosting.authenticated_token()
2022
+ return (
2023
+ authenticated_token[1].get("tier", "").lower()
2024
+ if authenticated_token[0]
2025
+ else "anonymous"
2026
+ )
2027
+
2028
+
2001
2029
  def check_config_option_in_tier(
2002
2030
  option_name: str,
2003
2031
  allowed_tiers: list[str],
@@ -2012,23 +2040,21 @@ def check_config_option_in_tier(
2012
2040
  fallback_value: The fallback value if the option is not allowed.
2013
2041
  help_link: The help link to show to a user that is authenticated.
2014
2042
  """
2015
- from reflex_cli.v2.utils import hosting
2016
-
2017
2043
  config = get_config()
2018
- authenticated_token = hosting.authenticated_token()
2019
- if not authenticated_token[0]:
2044
+ current_tier = get_user_tier()
2045
+
2046
+ if current_tier == "anonymous":
2020
2047
  the_remedy = (
2021
2048
  "You are currently logged out. Run `reflex login` to access this option."
2022
2049
  )
2023
- current_tier = "anonymous"
2024
2050
  else:
2025
- current_tier = authenticated_token[1].get("tier", "").lower()
2026
2051
  the_remedy = (
2027
2052
  f"Your current subscription tier is `{current_tier}`. "
2028
2053
  f"Please upgrade to {allowed_tiers} to access this option. "
2029
2054
  )
2030
2055
  if help_link:
2031
2056
  the_remedy += f"See {help_link} for more information."
2057
+
2032
2058
  if current_tier not in allowed_tiers:
2033
2059
  console.warn(f"Config option `{option_name}` is restricted. {the_remedy}")
2034
2060
  setattr(config, option_name, fallback_value)
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