reflex 0.8.14.post1__py3-none-any.whl → 0.8.15a1__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 (49) hide show
  1. reflex/__init__.py +12 -7
  2. reflex/__init__.pyi +11 -3
  3. reflex/app.py +5 -2
  4. reflex/base.py +58 -33
  5. reflex/components/datadisplay/dataeditor.py +17 -2
  6. reflex/components/datadisplay/dataeditor.pyi +6 -2
  7. reflex/components/field.py +3 -1
  8. reflex/components/lucide/icon.py +2 -1
  9. reflex/components/lucide/icon.pyi +2 -1
  10. reflex/components/markdown/markdown.py +101 -27
  11. reflex/components/sonner/toast.py +3 -2
  12. reflex/components/sonner/toast.pyi +3 -2
  13. reflex/constants/base.py +5 -0
  14. reflex/constants/installer.py +3 -3
  15. reflex/environment.py +9 -1
  16. reflex/event.py +3 -0
  17. reflex/istate/manager/__init__.py +120 -0
  18. reflex/istate/manager/disk.py +210 -0
  19. reflex/istate/manager/memory.py +76 -0
  20. reflex/istate/{manager.py → manager/redis.py} +5 -372
  21. reflex/istate/proxy.py +35 -24
  22. reflex/model.py +534 -511
  23. reflex/plugins/tailwind_v4.py +2 -2
  24. reflex/reflex.py +16 -10
  25. reflex/state.py +35 -34
  26. reflex/testing.py +12 -14
  27. reflex/utils/build.py +11 -1
  28. reflex/utils/compat.py +51 -48
  29. reflex/utils/misc.py +2 -1
  30. reflex/utils/monitoring.py +1 -2
  31. reflex/utils/prerequisites.py +19 -4
  32. reflex/utils/processes.py +3 -1
  33. reflex/utils/redir.py +21 -37
  34. reflex/utils/serializers.py +21 -20
  35. reflex/utils/telemetry.py +0 -2
  36. reflex/utils/templates.py +4 -4
  37. reflex/utils/types.py +82 -90
  38. reflex/vars/base.py +108 -41
  39. reflex/vars/color.py +28 -8
  40. reflex/vars/datetime.py +6 -2
  41. reflex/vars/dep_tracking.py +2 -2
  42. reflex/vars/number.py +26 -0
  43. reflex/vars/object.py +51 -7
  44. reflex/vars/sequence.py +32 -1
  45. {reflex-0.8.14.post1.dist-info → reflex-0.8.15a1.dist-info}/METADATA +8 -3
  46. {reflex-0.8.14.post1.dist-info → reflex-0.8.15a1.dist-info}/RECORD +49 -46
  47. {reflex-0.8.14.post1.dist-info → reflex-0.8.15a1.dist-info}/WHEEL +0 -0
  48. {reflex-0.8.14.post1.dist-info → reflex-0.8.15a1.dist-info}/entry_points.txt +0 -0
  49. {reflex-0.8.14.post1.dist-info → reflex-0.8.15a1.dist-info}/licenses/LICENSE +0 -0
@@ -17,7 +17,7 @@ class Constants(SimpleNamespace):
17
17
  """Tailwind constants."""
18
18
 
19
19
  # The Tailwindcss version
20
- VERSION = "tailwindcss@4.1.13"
20
+ VERSION = "tailwindcss@4.1.14"
21
21
  # The Tailwind config.
22
22
  CONFIG = "tailwind.config.js"
23
23
  # Default Tailwind content paths
@@ -156,7 +156,7 @@ class TailwindV4Plugin(TailwindPlugin):
156
156
  return [
157
157
  *super().get_frontend_development_dependencies(**context),
158
158
  Constants.VERSION,
159
- "@tailwindcss/postcss@4.1.13",
159
+ "@tailwindcss/postcss@4.1.14",
160
160
  ]
161
161
 
162
162
  def pre_compile(self, **context):
reflex/reflex.py CHANGED
@@ -11,12 +11,14 @@ from reflex_cli.v2.deployments import hosting_cli
11
11
 
12
12
  from reflex import constants
13
13
  from reflex.config import get_config
14
- from reflex.constants.base import LITERAL_ENV
15
14
  from reflex.custom_components.custom_components import custom_components_cli
16
15
  from reflex.environment import environment
17
- from reflex.state import reset_disk_state_manager
18
- from reflex.utils import console, redir, telemetry
19
- from reflex.utils.exec import should_use_granian
16
+ from reflex.utils import console
17
+
18
+ if TYPE_CHECKING:
19
+ from reflex_cli.constants.base import LogLevel as HostingLogLevel
20
+
21
+ from reflex.constants.base import LITERAL_ENV
20
22
 
21
23
 
22
24
  def set_loglevel(ctx: click.Context, self: click.Parameter, value: str | None):
@@ -40,6 +42,8 @@ def cli():
40
42
 
41
43
  loglevel_option = click.option(
42
44
  "--loglevel",
45
+ "--log-level",
46
+ "loglevel",
43
47
  type=click.Choice(
44
48
  [loglevel.value for loglevel in constants.LogLevel],
45
49
  case_sensitive=False,
@@ -63,7 +67,9 @@ def _init(
63
67
  exec.output_system_info()
64
68
 
65
69
  if ai:
66
- redir.reflex_build_redirect()
70
+ from reflex.utils.redir import reflex_build_redirect
71
+
72
+ reflex_build_redirect()
67
73
  return
68
74
 
69
75
  # Validate the app name.
@@ -136,7 +142,9 @@ def _run(
136
142
  """Run the app in the given directory."""
137
143
  import atexit
138
144
 
139
- from reflex.utils import build, exec, prerequisites, processes
145
+ from reflex.state import reset_disk_state_manager
146
+ from reflex.utils import build, exec, prerequisites, processes, telemetry
147
+ from reflex.utils.exec import should_use_granian
140
148
 
141
149
  config = get_config()
142
150
 
@@ -535,6 +543,8 @@ def login():
535
543
  validated_info = hosting_cli.login()
536
544
  if validated_info is not None:
537
545
  _skip_compile() # Allow running outside of an app dir
546
+ from reflex.utils import telemetry
547
+
538
548
  telemetry.send("login", user_uuid=validated_info.get("user_id"))
539
549
 
540
550
 
@@ -838,10 +848,6 @@ def rename(new_name: str):
838
848
  rename_app(new_name, get_config().loglevel)
839
849
 
840
850
 
841
- if TYPE_CHECKING:
842
- from reflex_cli.constants.base import LogLevel as HostingLogLevel
843
-
844
-
845
851
  def _convert_reflex_loglevel_to_reflex_cli_loglevel(
846
852
  loglevel: constants.LogLevel,
847
853
  ) -> HostingLogLevel:
reflex/state.py CHANGED
@@ -16,20 +16,17 @@ import time
16
16
  import typing
17
17
  import warnings
18
18
  from collections.abc import AsyncIterator, Callable, Sequence
19
+ from enum import Enum
19
20
  from hashlib import md5
21
+ from importlib.util import find_spec
20
22
  from types import FunctionType
21
23
  from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, TypeVar, cast, get_type_hints
22
24
 
23
- import pydantic.v1 as pydantic
24
- from pydantic import BaseModel as BaseModelV2
25
- from pydantic.v1 import BaseModel as BaseModelV1
26
- from pydantic.v1.fields import ModelField
27
25
  from rich.markup import escape
28
26
  from typing_extensions import Self
29
27
 
30
28
  import reflex.istate.dynamic
31
29
  from reflex import constants, event
32
- from reflex.base import Base
33
30
  from reflex.constants.state import FIELD_MARKER
34
31
  from reflex.environment import PerformanceMode, environment
35
32
  from reflex.event import (
@@ -94,13 +91,11 @@ if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
94
91
  VAR_TYPE = TypeVar("VAR_TYPE")
95
92
 
96
93
 
97
- def _no_chain_background_task(
98
- state_cls: type[BaseState], name: str, fn: Callable
99
- ) -> Callable:
94
+ def _no_chain_background_task(state: BaseState, name: str, fn: Callable) -> Callable:
100
95
  """Protect against directly chaining a background task from another event handler.
101
96
 
102
97
  Args:
103
- state_cls: The state class that the event handler is in.
98
+ state: The state instance the background task is bound to.
104
99
  name: The name of the background task.
105
100
  fn: The background task coroutine function / generator.
106
101
 
@@ -110,7 +105,7 @@ def _no_chain_background_task(
110
105
  Raises:
111
106
  TypeError: If the background task is not async.
112
107
  """
113
- call = f"{state_cls.__name__}.{name}"
108
+ call = f"{type(state).__name__}.{name}"
114
109
  message = (
115
110
  f"Cannot directly call background task {name!r}, use "
116
111
  f"`yield {call}` or `return {call}` instead."
@@ -252,17 +247,13 @@ class EventHandlerSetVar(EventHandler):
252
247
  msg = f"Variable `{args[0]}` cannot be set on `{self.state_cls.get_full_name()}`"
253
248
  raise AttributeError(msg)
254
249
 
255
- if asyncio.iscoroutinefunction(handler.fn):
250
+ if inspect.iscoroutinefunction(handler.fn):
256
251
  msg = f"Setter for {args[0]} is async, which is not supported."
257
252
  raise NotImplementedError(msg)
258
253
 
259
254
  return super().__call__(*args)
260
255
 
261
256
 
262
- if TYPE_CHECKING:
263
- from pydantic.v1.fields import ModelField
264
-
265
-
266
257
  def get_var_for_field(cls: type[BaseState], name: str, f: Field) -> Var:
267
258
  """Get a Var instance for a state field.
268
259
 
@@ -297,7 +288,7 @@ async def _resolve_delta(delta: Delta) -> Delta:
297
288
  tasks = {}
298
289
  for state_name, state_delta in delta.items():
299
290
  for var_name, value in state_delta.items():
300
- if asyncio.iscoroutine(value):
291
+ if inspect.iscoroutine(value):
301
292
  tasks[state_name, var_name] = asyncio.create_task(
302
293
  value,
303
294
  name=f"reflex_resolve_delta|{state_name}|{var_name}|{time.time()}",
@@ -479,7 +470,7 @@ class BaseState(EvenMoreBasicBaseState):
479
470
 
480
471
  Args:
481
472
  mixin: Whether the subclass is a mixin and should not be initialized.
482
- **kwargs: The kwargs to pass to the pydantic init_subclass method.
473
+ **kwargs: The kwargs to pass to the init_subclass method.
483
474
 
484
475
  Raises:
485
476
  StateValueError: If a substate class shadows another.
@@ -739,7 +730,7 @@ class BaseState(EvenMoreBasicBaseState):
739
730
  mixin
740
731
  for mixin in cls.__mro__
741
732
  if (
742
- mixin not in [pydantic.BaseModel, Base, cls]
733
+ mixin is not cls
743
734
  and issubclass(mixin, BaseState)
744
735
  and mixin._mixin is True
745
736
  )
@@ -862,7 +853,7 @@ class BaseState(EvenMoreBasicBaseState):
862
853
  ComputedVarShadowsBaseVarsError: When a computed var shadows a base var.
863
854
  """
864
855
  for name, computed_var_ in cls._get_computed_vars():
865
- if name in cls.__annotations__:
856
+ if name in get_type_hints(cls):
866
857
  msg = f"The computed var name `{computed_var_._js_expr}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead"
867
858
  raise ComputedVarShadowsBaseVarsError(msg)
868
859
 
@@ -1090,7 +1081,7 @@ class BaseState(EvenMoreBasicBaseState):
1090
1081
  _var_data=VarData.from_state(cls, name),
1091
1082
  ).guess_type()
1092
1083
 
1093
- # add the pydantic field dynamically (must be done before _init_var)
1084
+ # add the field dynamically (must be done before _init_var)
1094
1085
  cls.add_field(name, var, default_value)
1095
1086
 
1096
1087
  cls._init_var(name, var)
@@ -1188,7 +1179,7 @@ class BaseState(EvenMoreBasicBaseState):
1188
1179
  name: The name of the var.
1189
1180
  prop: The var to set the default value for.
1190
1181
  """
1191
- # Get the pydantic field for the var.
1182
+ # Get the field for the var.
1192
1183
  field = cls.get_fields()[name]
1193
1184
 
1194
1185
  if field.default is None and not types.is_optional(prop._var_type):
@@ -1349,7 +1340,7 @@ class BaseState(EvenMoreBasicBaseState):
1349
1340
  if name in event_handlers:
1350
1341
  handler = event_handlers[name]
1351
1342
  if handler.is_background:
1352
- fn = _no_chain_background_task(type(self), name, handler.fn)
1343
+ fn = _no_chain_background_task(self, name, handler.fn)
1353
1344
  else:
1354
1345
  fn = functools.partial(handler.fn, self)
1355
1346
  fn.__module__ = handler.fn.__module__
@@ -1465,7 +1456,7 @@ class BaseState(EvenMoreBasicBaseState):
1465
1456
 
1466
1457
  @classmethod
1467
1458
  @functools.lru_cache
1468
- def _is_client_storage(cls, prop_name_or_field: str | ModelField) -> bool:
1459
+ def _is_client_storage(cls, prop_name_or_field: str | Field) -> bool:
1469
1460
  """Check if the var is a client storage var.
1470
1461
 
1471
1462
  Args:
@@ -1564,6 +1555,8 @@ class BaseState(EvenMoreBasicBaseState):
1564
1555
  RuntimeError: If redis is not used in this backend process.
1565
1556
  StateMismatchError: If the state instance is not of the expected type.
1566
1557
  """
1558
+ from reflex.istate.manager.redis import StateManagerRedis
1559
+
1567
1560
  # Then get the target state and all its substates.
1568
1561
  state_manager = get_state_manager()
1569
1562
  if not isinstance(state_manager, StateManagerRedis):
@@ -1743,7 +1736,7 @@ class BaseState(EvenMoreBasicBaseState):
1743
1736
  except TypeError:
1744
1737
  pass
1745
1738
 
1746
- coroutines = [e for e in events if asyncio.iscoroutine(e)]
1739
+ coroutines = [e for e in events if inspect.iscoroutine(e)]
1747
1740
 
1748
1741
  for coroutine in coroutines:
1749
1742
  coroutine_name = coroutine.__qualname__
@@ -1872,16 +1865,28 @@ class BaseState(EvenMoreBasicBaseState):
1872
1865
  if key in hinted_args.__fields__
1873
1866
  }
1874
1867
  )
1875
- elif dataclasses.is_dataclass(hinted_args) or issubclass(
1876
- hinted_args, (Base, BaseModelV1, BaseModelV2)
1877
- ):
1868
+ elif dataclasses.is_dataclass(hinted_args):
1878
1869
  payload[arg] = hinted_args(**value)
1870
+ elif find_spec("pydantic"):
1871
+ from pydantic import BaseModel as BaseModelV2
1872
+ from pydantic.v1 import BaseModel as BaseModelV1
1873
+
1874
+ if issubclass(hinted_args, BaseModelV1):
1875
+ payload[arg] = hinted_args.parse_obj(value)
1876
+ elif issubclass(hinted_args, BaseModelV2):
1877
+ payload[arg] = hinted_args.model_validate(value)
1879
1878
  elif isinstance(value, list) and (hinted_args is set or hinted_args is set):
1880
1879
  payload[arg] = set(value)
1881
1880
  elif isinstance(value, list) and (
1882
1881
  hinted_args is tuple or hinted_args is tuple
1883
1882
  ):
1884
1883
  payload[arg] = tuple(value)
1884
+ elif isinstance(hinted_args, type) and issubclass(hinted_args, Enum):
1885
+ try:
1886
+ payload[arg] = hinted_args(value)
1887
+ except ValueError:
1888
+ msg = f"Received an invalid enum value ({value}) for {arg} of type {hinted_args}"
1889
+ raise ValueError(msg) from None
1885
1890
  elif (
1886
1891
  isinstance(value, str)
1887
1892
  and (deserializer := _deserializers.get(hinted_args)) is not None
@@ -1899,7 +1904,7 @@ class BaseState(EvenMoreBasicBaseState):
1899
1904
  # Wrap the function in a try/except block.
1900
1905
  try:
1901
1906
  # Handle async functions.
1902
- if asyncio.iscoroutinefunction(fn.func):
1907
+ if inspect.iscoroutinefunction(fn.func):
1903
1908
  events = await fn(**payload)
1904
1909
 
1905
1910
  # Handle regular functions.
@@ -2142,7 +2147,7 @@ class BaseState(EvenMoreBasicBaseState):
2142
2147
  Args:
2143
2148
  include_computed: Whether to include computed vars.
2144
2149
  initial: Whether to get the initial value of computed vars.
2145
- **kwargs: Kwargs to pass to the pydantic dict method.
2150
+ **kwargs: Kwargs to pass to the dict method.
2146
2151
 
2147
2152
  Returns:
2148
2153
  The object as a dictionary.
@@ -2626,7 +2631,7 @@ class ComponentState(State, mixin=True):
2626
2631
 
2627
2632
  Args:
2628
2633
  mixin: Whether the subclass is a mixin and should not be initialized.
2629
- **kwargs: The kwargs to pass to the pydantic init_subclass method.
2634
+ **kwargs: The kwargs to pass to the init_subclass method.
2630
2635
  """
2631
2636
  super().__init_subclass__(mixin=mixin, **kwargs)
2632
2637
 
@@ -2742,11 +2747,7 @@ def reload_state_module(
2742
2747
  state.get_class_substate.cache_clear()
2743
2748
 
2744
2749
 
2745
- from reflex.istate.manager import LockExpiredError as LockExpiredError # noqa: E402
2746
2750
  from reflex.istate.manager import StateManager as StateManager # noqa: E402
2747
- from reflex.istate.manager import StateManagerDisk as StateManagerDisk # noqa: E402
2748
- from reflex.istate.manager import StateManagerMemory as StateManagerMemory # noqa: E402
2749
- from reflex.istate.manager import StateManagerRedis as StateManagerRedis # noqa: E402
2750
2751
  from reflex.istate.manager import get_state_manager as get_state_manager # noqa: E402
2751
2752
  from reflex.istate.manager import ( # noqa: E402
2752
2753
  reset_disk_state_manager as reset_disk_state_manager,
reflex/testing.py CHANGED
@@ -21,6 +21,7 @@ import time
21
21
  import types
22
22
  from collections.abc import AsyncIterator, Callable, Coroutine, Sequence
23
23
  from http.server import SimpleHTTPRequestHandler
24
+ from importlib.util import find_spec
24
25
  from pathlib import Path
25
26
  from typing import TYPE_CHECKING, Any, Literal, TypeVar
26
27
 
@@ -37,14 +38,10 @@ import reflex.utils.processes
37
38
  from reflex.components.component import CustomComponent
38
39
  from reflex.config import get_config
39
40
  from reflex.environment import environment
40
- from reflex.state import (
41
- BaseState,
42
- StateManager,
43
- StateManagerDisk,
44
- StateManagerMemory,
45
- StateManagerRedis,
46
- reload_state_module,
47
- )
41
+ from reflex.istate.manager.disk import StateManagerDisk
42
+ from reflex.istate.manager.memory import StateManagerMemory
43
+ from reflex.istate.manager.redis import StateManagerRedis
44
+ from reflex.state import BaseState, StateManager, reload_state_module
48
45
  from reflex.utils import console, js_runtimes
49
46
  from reflex.utils.export import export
50
47
  from reflex.utils.token_manager import TokenManager
@@ -320,12 +317,13 @@ class AppHarness:
320
317
  await self.app_instance.sio.shutdown()
321
318
 
322
319
  # sqlalchemy async engine shutdown handler
323
- try:
324
- async_engine = reflex.model.get_async_engine(None)
325
- except ValueError:
326
- pass
327
- else:
328
- await async_engine.dispose()
320
+ if find_spec("sqlmodel"):
321
+ try:
322
+ async_engine = reflex.model.get_async_engine(None)
323
+ except ValueError:
324
+ pass
325
+ else:
326
+ await async_engine.dispose()
329
327
 
330
328
  await original_shutdown(*args, **kwargs)
331
329
 
reflex/utils/build.py CHANGED
@@ -187,7 +187,11 @@ def _duplicate_index_html_to_parent_directory(directory: Path):
187
187
 
188
188
 
189
189
  def build():
190
- """Build the app for deployment."""
190
+ """Build the app for deployment.
191
+
192
+ Raises:
193
+ SystemExit: If the build process fails.
194
+ """
191
195
  wdir = prerequisites.get_web_dir()
192
196
 
193
197
  # Clean the static directory if it exists.
@@ -214,6 +218,12 @@ def build():
214
218
  },
215
219
  )
216
220
  processes.show_progress("Creating Production Build", process, checkpoints)
221
+ process.wait()
222
+ if process.returncode != 0:
223
+ console.error(
224
+ "Failed to build the frontend. Please run with --loglevel debug for more information.",
225
+ )
226
+ raise SystemExit(1)
217
227
  _duplicate_index_html_to_parent_directory(wdir / constants.Dirs.STATIC)
218
228
 
219
229
  spa_fallback = wdir / constants.Dirs.STATIC / constants.ReactRouter.SPA_FALLBACK
reflex/utils/compat.py CHANGED
@@ -1,8 +1,12 @@
1
1
  """Compatibility hacks and helpers."""
2
2
 
3
- import contextlib
4
3
  import sys
5
- from typing import Any
4
+ from collections.abc import Mapping
5
+ from importlib.util import find_spec
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ if TYPE_CHECKING:
9
+ from pydantic.fields import FieldInfo
6
10
 
7
11
 
8
12
  async def windows_hot_reload_lifespan_hack():
@@ -29,63 +33,62 @@ async def windows_hot_reload_lifespan_hack():
29
33
  pass
30
34
 
31
35
 
32
- @contextlib.contextmanager
33
- def pydantic_v1_patch():
34
- """A context manager that patches the Pydantic module to mimic v1 behaviour.
36
+ def annotations_from_namespace(namespace: Mapping[str, Any]) -> dict[str, Any]:
37
+ """Get the annotations from a class namespace.
38
+
39
+ Args:
40
+ namespace: The class namespace.
35
41
 
36
- Yields:
37
- None when the Pydantic module is patched.
42
+ Returns:
43
+ The (forward-ref) annotations from the class namespace.
38
44
  """
39
- import pydantic
40
-
41
- if pydantic.__version__.startswith("1."):
42
- # pydantic v1 is already installed
43
- yield
44
- return
45
-
46
- patched_modules = [
47
- "pydantic",
48
- "pydantic.fields",
49
- "pydantic.errors",
50
- "pydantic.main",
51
- ]
52
- originals = {module: sys.modules.get(module) for module in patched_modules}
53
- try:
54
- import pydantic.v1
45
+ if sys.version_info >= (3, 14) and "__annotations__" not in namespace:
46
+ from annotationlib import (
47
+ Format,
48
+ call_annotate_function,
49
+ get_annotate_from_class_namespace,
50
+ )
51
+
52
+ if annotate := get_annotate_from_class_namespace(namespace):
53
+ return call_annotate_function(annotate, format=Format.FORWARDREF)
54
+ return namespace.get("__annotations__", {})
55
+
56
+
57
+ if find_spec("pydantic") and find_spec("pydantic.v1"):
58
+ from pydantic.v1.main import ModelMetaclass
59
+
60
+ class ModelMetaclassLazyAnnotations(ModelMetaclass):
61
+ """Compatibility metaclass to resolve python3.14 style lazy annotations."""
55
62
 
56
- sys.modules["pydantic.fields"] = pydantic.v1.fields # pyright: ignore [reportAttributeAccessIssue]
57
- sys.modules["pydantic.main"] = pydantic.v1.main # pyright: ignore [reportAttributeAccessIssue]
58
- sys.modules["pydantic.errors"] = pydantic.v1.errors # pyright: ignore [reportAttributeAccessIssue]
59
- sys.modules["pydantic"] = pydantic.v1
60
- yield
61
- except (ImportError, AttributeError):
62
- # pydantic v1 is already installed
63
- yield
64
- finally:
65
- # Restore the original Pydantic module
66
- for k, original in originals.items():
67
- if k in sys.modules:
68
- if original:
69
- sys.modules[k] = original
70
- else:
71
- del sys.modules[k]
63
+ def __new__(mcs, name: str, bases: tuple, namespace: dict, **kwargs):
64
+ """Resolve python3.14 style lazy annotations before passing off to pydantic v1.
72
65
 
66
+ Args:
67
+ name: The class name.
68
+ bases: The base classes.
69
+ namespace: The class namespace.
70
+ **kwargs: Additional keyword arguments.
73
71
 
74
- with pydantic_v1_patch():
75
- import sqlmodel as sqlmodel
72
+ Returns:
73
+ The created class.
74
+ """
75
+ namespace["__annotations__"] = annotations_from_namespace(namespace)
76
+ return super().__new__(mcs, name, bases, namespace, **kwargs)
77
+ else:
78
+ ModelMetaclassLazyAnnotations = type # type: ignore[assignment]
76
79
 
77
80
 
78
- def sqlmodel_field_has_primary_key(field: Any) -> bool:
79
- """Determines if a field is a priamary.
81
+ def sqlmodel_field_has_primary_key(field_info: "FieldInfo") -> bool:
82
+ """Determines if a field is a primary.
80
83
 
81
84
  Args:
82
- field: a rx.model field
85
+ field_info: a rx.model field
83
86
 
84
87
  Returns:
85
- If field is a primary key (Bool)
88
+ If field_info is a primary key (Bool)
86
89
  """
87
- if getattr(field.field_info, "primary_key", None) is True:
90
+ if getattr(field_info, "primary_key", None) is True:
88
91
  return True
89
- if getattr(field.field_info, "sa_column", None) is None:
92
+ if getattr(field_info, "sa_column", None) is None:
90
93
  return False
91
- return bool(getattr(field.field_info.sa_column, "primary_key", None))
94
+ return bool(getattr(field_info.sa_column, "primary_key", None)) # pyright: ignore[reportAttributeAccessIssue]
reflex/utils/misc.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import asyncio
4
4
  import contextlib
5
+ import inspect
5
6
  import sys
6
7
  import threading
7
8
  from collections.abc import Callable
@@ -62,7 +63,7 @@ async def run_in_thread(func: Callable) -> Any:
62
63
  Returns:
63
64
  Any: The return value of the function.
64
65
  """
65
- if asyncio.coroutines.iscoroutinefunction(func):
66
+ if inspect.iscoroutinefunction(func):
66
67
  msg = "func must be a non-async function"
67
68
  raise ValueError(msg)
68
69
  return await asyncio.get_event_loop().run_in_executor(None, func)
@@ -1,6 +1,5 @@
1
1
  """PyLeak integration for monitoring event loop blocking and resource leaks in Reflex applications."""
2
2
 
3
- import asyncio
4
3
  import contextlib
5
4
  import functools
6
5
  import inspect
@@ -154,7 +153,7 @@ def monitor_loopblocks(func: Callable) -> Callable:
154
153
 
155
154
  return async_gen_wrapper
156
155
 
157
- if asyncio.iscoroutinefunction(func):
156
+ if inspect.iscoroutinefunction(func):
158
157
 
159
158
  @functools.wraps(func)
160
159
  async def async_wrapper(*args, **kwargs):
@@ -15,11 +15,7 @@ from pathlib import Path
15
15
  from types import ModuleType
16
16
  from typing import NamedTuple
17
17
 
18
- from alembic.util.exc import CommandError
19
18
  from packaging import version
20
- from redis import Redis as RedisSync
21
- from redis.asyncio import Redis
22
- from redis.exceptions import RedisError
23
19
 
24
20
  from reflex import constants, model
25
21
  from reflex.config import Config, get_config
@@ -29,6 +25,9 @@ from reflex.utils.decorator import once
29
25
  from reflex.utils.misc import get_module_path
30
26
 
31
27
  if typing.TYPE_CHECKING:
28
+ from redis import Redis as RedisSync
29
+ from redis.asyncio import Redis
30
+
32
31
  from reflex.app import App
33
32
 
34
33
 
@@ -370,6 +369,12 @@ def get_redis() -> Redis | None:
370
369
  Returns:
371
370
  The asynchronous redis client.
372
371
  """
372
+ try:
373
+ from redis.asyncio import Redis
374
+ from redis.exceptions import RedisError
375
+ except ImportError:
376
+ console.debug("Redis package not installed.")
377
+ return None
373
378
  if (redis_url := parse_redis_url()) is not None:
374
379
  return Redis.from_url(
375
380
  redis_url,
@@ -384,6 +389,12 @@ def get_redis_sync() -> RedisSync | None:
384
389
  Returns:
385
390
  The synchronous redis client.
386
391
  """
392
+ try:
393
+ from redis import Redis as RedisSync
394
+ from redis.exceptions import RedisError
395
+ except ImportError:
396
+ console.debug("Redis package not installed.")
397
+ return None
387
398
  if (redis_url := parse_redis_url()) is not None:
388
399
  return RedisSync.from_url(
389
400
  redis_url,
@@ -418,6 +429,8 @@ async def get_redis_status() -> dict[str, bool | None]:
418
429
  Returns:
419
430
  The status of the Redis connection.
420
431
  """
432
+ from redis.exceptions import RedisError
433
+
421
434
  try:
422
435
  status = True
423
436
  redis_client = get_redis()
@@ -639,6 +652,8 @@ def check_schema_up_to_date():
639
652
  if get_config().db_url is None or not environment.ALEMBIC_CONFIG.get().exists():
640
653
  return
641
654
  with model.Model.get_db_engine().connect() as connection:
655
+ from alembic.util.exc import CommandError
656
+
642
657
  try:
643
658
  if model.Model.alembic_autogenerate(
644
659
  connection=connection,
reflex/utils/processes.py CHANGED
@@ -16,7 +16,6 @@ from pathlib import Path
16
16
  from typing import Any, Literal, overload
17
17
 
18
18
  import rich.markup
19
- from redis.exceptions import RedisError
20
19
  from rich.progress import Progress
21
20
 
22
21
  from reflex import constants
@@ -45,6 +44,9 @@ def get_num_workers() -> int:
45
44
  """
46
45
  if (redis_client := prerequisites.get_redis_sync()) is None:
47
46
  return 1
47
+
48
+ from redis.exceptions import RedisError
49
+
48
50
  try:
49
51
  redis_client.ping()
50
52
  except RedisError as re: