reflex 0.7.0a5__py3-none-any.whl → 0.7.1a1__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 +250 -67
  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 +35 -5
  16. reflex/components/core/banner.pyi +398 -36
  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 +19 -1
  90. reflex/components/sonner/toast.pyi +10 -1
  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 +6 -4
  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.1a1.dist-info}/METADATA +4 -5
  123. {reflex-0.7.0a5.dist-info → reflex-0.7.1a1.dist-info}/RECORD +126 -122
  124. {reflex-0.7.0a5.dist-info → reflex-0.7.1a1.dist-info}/LICENSE +0 -0
  125. {reflex-0.7.0a5.dist-info → reflex-0.7.1a1.dist-info}/WHEEL +0 -0
  126. {reflex-0.7.0a5.dist-info → reflex-0.7.1a1.dist-info}/entry_points.txt +0 -0
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
 
reflex/utils/types.py CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import contextlib
6
5
  import dataclasses
7
6
  import inspect
8
7
  import sys
9
8
  import types
10
9
  from functools import cached_property, lru_cache, wraps
10
+ from types import GenericAlias
11
11
  from typing import (
12
12
  TYPE_CHECKING,
13
13
  Any,
@@ -25,66 +25,29 @@ from typing import (
25
25
  Type,
26
26
  Union,
27
27
  _GenericAlias, # pyright: ignore [reportAttributeAccessIssue]
28
+ _SpecialGenericAlias, # pyright: ignore [reportAttributeAccessIssue]
28
29
  get_args,
29
30
  get_type_hints,
30
31
  )
31
32
  from typing import get_origin as get_origin_og
32
33
 
33
34
  import sqlalchemy
34
- from typing_extensions import is_typeddict
35
-
36
- import reflex
37
- from reflex.components.core.breakpoints import Breakpoints
38
-
39
- try:
40
- from pydantic.v1.fields import ModelField
41
- except ModuleNotFoundError:
42
- from pydantic.fields import (
43
- ModelField, # pyright: ignore [reportAttributeAccessIssue]
44
- )
45
-
35
+ from pydantic.v1.fields import ModelField
46
36
  from sqlalchemy.ext.associationproxy import AssociationProxyInstance
47
37
  from sqlalchemy.ext.hybrid import hybrid_property
48
38
  from sqlalchemy.orm import DeclarativeBase, Mapped, QueryableAttribute, Relationship
39
+ from typing_extensions import Self as Self
40
+ from typing_extensions import is_typeddict
41
+ from typing_extensions import override as override
49
42
 
43
+ import reflex
50
44
  from reflex import constants
51
45
  from reflex.base import Base
46
+ from reflex.components.core.breakpoints import Breakpoints
52
47
  from reflex.utils import console
53
48
 
54
- if sys.version_info >= (3, 12):
55
- from typing import override as override
56
- else:
57
-
58
- def override(func: Callable) -> Callable:
59
- """Fallback for @override decorator.
60
-
61
- Args:
62
- func: The function to decorate.
63
-
64
- Returns:
65
- The unmodified function.
66
- """
67
- return func
68
-
69
-
70
49
  # Potential GenericAlias types for isinstance checks.
71
- GenericAliasTypes = [_GenericAlias]
72
-
73
- with contextlib.suppress(ImportError):
74
- # For newer versions of Python.
75
- from types import GenericAlias
76
-
77
- GenericAliasTypes.append(GenericAlias)
78
-
79
- with contextlib.suppress(ImportError):
80
- # For older versions of Python.
81
- from typing import (
82
- _SpecialGenericAlias, # pyright: ignore [reportAttributeAccessIssue]
83
- )
84
-
85
- GenericAliasTypes.append(_SpecialGenericAlias)
86
-
87
- GenericAliasTypes = tuple(GenericAliasTypes)
50
+ GenericAliasTypes = (_GenericAlias, GenericAlias, _SpecialGenericAlias)
88
51
 
89
52
  # Potential Union types for isinstance checks (UnionType added in py3.10).
90
53
  UnionTypes = (Union, types.UnionType) if hasattr(types, "UnionType") else (Union,)
@@ -95,6 +58,7 @@ GenericType = Union[Type, _GenericAlias]
95
58
  # Valid state var types.
96
59
  JSONType = {str, int, float, bool}
97
60
  PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple]
61
+ PrimitiveTypes = (int, float, bool, str, list, dict, set, tuple)
98
62
  StateVar = Union[PrimitiveType, Base, None]
99
63
  StateIterVar = Union[list, set, tuple]
100
64
 
@@ -127,11 +91,6 @@ RESERVED_BACKEND_VAR_NAMES = {
127
91
  "_was_touched",
128
92
  }
129
93
 
130
- if sys.version_info >= (3, 11):
131
- from typing import Self as Self
132
- else:
133
- from typing_extensions import Self as Self
134
-
135
94
 
136
95
  class Unset:
137
96
  """A class to represent an unset value.
@@ -551,13 +510,13 @@ def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool:
551
510
  return required_keys.issubset(required_keys)
552
511
 
553
512
 
554
- def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
513
+ def _isinstance(obj: Any, cls: GenericType, nested: int = 0) -> bool:
555
514
  """Check if an object is an instance of a class.
556
515
 
557
516
  Args:
558
517
  obj: The object to check.
559
518
  cls: The class to check against.
560
- nested: Whether the check is nested.
519
+ nested: How many levels deep to check.
561
520
 
562
521
  Returns:
563
522
  Whether the object is an instance of the class.
@@ -565,15 +524,24 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
565
524
  if cls is Any:
566
525
  return True
567
526
 
527
+ from reflex.vars import LiteralVar, Var
528
+
529
+ if cls is Var:
530
+ return isinstance(obj, Var)
531
+ if isinstance(obj, LiteralVar):
532
+ return _isinstance(obj._var_value, cls, nested=nested)
533
+ if isinstance(obj, Var):
534
+ return _issubclass(obj._var_type, cls)
535
+
568
536
  if cls is None or cls is type(None):
569
537
  return obj is None
570
538
 
539
+ if cls and is_union(cls):
540
+ return any(_isinstance(obj, arg, nested=nested) for arg in get_args(cls))
541
+
571
542
  if is_literal(cls):
572
543
  return obj in get_args(cls)
573
544
 
574
- if is_union(cls):
575
- return any(_isinstance(obj, arg) for arg in get_args(cls))
576
-
577
545
  origin = get_origin(cls)
578
546
 
579
547
  if origin is None:
@@ -596,38 +564,40 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
596
564
  # cls is a simple generic class
597
565
  return isinstance(obj, origin)
598
566
 
599
- if nested and args:
567
+ if nested > 0 and args:
600
568
  if origin is list:
601
569
  return isinstance(obj, list) and all(
602
- _isinstance(item, args[0]) for item in obj
570
+ _isinstance(item, args[0], nested=nested - 1) for item in obj
603
571
  )
604
572
  if origin is tuple:
605
573
  if args[-1] is Ellipsis:
606
574
  return isinstance(obj, tuple) and all(
607
- _isinstance(item, args[0]) for item in obj
575
+ _isinstance(item, args[0], nested=nested - 1) for item in obj
608
576
  )
609
577
  return (
610
578
  isinstance(obj, tuple)
611
579
  and len(obj) == len(args)
612
580
  and all(
613
- _isinstance(item, arg) for item, arg in zip(obj, args, strict=True)
581
+ _isinstance(item, arg, nested=nested - 1)
582
+ for item, arg in zip(obj, args, strict=True)
614
583
  )
615
584
  )
616
- if origin in (dict, Breakpoints):
617
- return isinstance(obj, dict) and all(
618
- _isinstance(key, args[0]) and _isinstance(value, args[1])
585
+ if origin in (dict, Mapping, Breakpoints):
586
+ return isinstance(obj, Mapping) and all(
587
+ _isinstance(key, args[0], nested=nested - 1)
588
+ and _isinstance(value, args[1], nested=nested - 1)
619
589
  for key, value in obj.items()
620
590
  )
621
591
  if origin is set:
622
592
  return isinstance(obj, set) and all(
623
- _isinstance(item, args[0]) for item in obj
593
+ _isinstance(item, args[0], nested=nested - 1) for item in obj
624
594
  )
625
595
 
626
596
  if args:
627
597
  from reflex.vars import Field
628
598
 
629
599
  if origin is Field:
630
- return _isinstance(obj, args[0])
600
+ return _isinstance(obj, args[0], nested=nested)
631
601
 
632
602
  return isinstance(obj, get_base_class(cls))
633
603
 
@@ -749,7 +719,7 @@ def check_prop_in_allowed_types(prop: Any, allowed_types: Iterable) -> bool:
749
719
  """
750
720
  from reflex.vars import Var
751
721
 
752
- type_ = prop._var_type if _isinstance(prop, Var) else type(prop)
722
+ type_ = prop._var_type if isinstance(prop, Var) else type(prop)
753
723
  return type_ in allowed_types
754
724
 
755
725