reflex 0.6.7a2__py3-none-any.whl → 0.6.8__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 (62) hide show
  1. reflex/.templates/jinja/web/pages/_app.js.jinja2 +2 -4
  2. reflex/.templates/jinja/web/pages/custom_component.js.jinja2 +3 -4
  3. reflex/.templates/jinja/web/pages/index.js.jinja2 +2 -3
  4. reflex/.templates/jinja/web/pages/macros.js.jinja2 +38 -0
  5. reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +4 -16
  6. reflex/.templates/jinja/web/utils/context.js.jinja2 +1 -1
  7. reflex/.templates/web/utils/state.js +9 -4
  8. reflex/app.py +12 -10
  9. reflex/compiler/compiler.py +2 -2
  10. reflex/compiler/templates.py +41 -0
  11. reflex/compiler/utils.py +1 -1
  12. reflex/components/base/bare.py +7 -3
  13. reflex/components/component.py +78 -116
  14. reflex/components/core/banner.py +1 -1
  15. reflex/components/core/breakpoints.py +1 -1
  16. reflex/components/datadisplay/code.py +14 -10
  17. reflex/components/datadisplay/dataeditor.py +1 -1
  18. reflex/components/datadisplay/dataeditor.pyi +1 -1
  19. reflex/components/el/elements/forms.py +7 -5
  20. reflex/components/el/elements/metadata.py +1 -1
  21. reflex/components/lucide/icon.py +142 -19
  22. reflex/components/lucide/icon.pyi +141 -18
  23. reflex/components/markdown/markdown.py +3 -2
  24. reflex/components/plotly/plotly.py +3 -3
  25. reflex/components/plotly/plotly.pyi +3 -3
  26. reflex/components/radix/primitives/slider.py +1 -1
  27. reflex/components/radix/themes/layout/center.pyi +1 -1
  28. reflex/components/radix/themes/layout/flex.py +1 -1
  29. reflex/components/radix/themes/layout/flex.pyi +1 -1
  30. reflex/components/radix/themes/layout/grid.py +1 -1
  31. reflex/components/radix/themes/layout/grid.pyi +1 -1
  32. reflex/components/radix/themes/layout/spacer.pyi +1 -1
  33. reflex/components/radix/themes/layout/stack.pyi +3 -3
  34. reflex/components/radix/themes/typography/link.py +1 -1
  35. reflex/components/recharts/cartesian.py +2 -2
  36. reflex/components/recharts/cartesian.pyi +6 -6
  37. reflex/components/recharts/charts.py +2 -2
  38. reflex/components/recharts/polar.py +2 -2
  39. reflex/components/recharts/polar.pyi +2 -2
  40. reflex/components/sonner/toast.py +1 -1
  41. reflex/constants/base.py +1 -1
  42. reflex/constants/compiler.py +1 -0
  43. reflex/event.py +96 -1
  44. reflex/experimental/client_state.py +42 -20
  45. reflex/istate/data.py +3 -3
  46. reflex/model.py +4 -5
  47. reflex/page.py +1 -1
  48. reflex/reflex.py +8 -1
  49. reflex/state.py +116 -9
  50. reflex/testing.py +3 -3
  51. reflex/utils/exceptions.py +4 -0
  52. reflex/utils/prerequisites.py +37 -20
  53. reflex/utils/processes.py +2 -2
  54. reflex/utils/pyi_generator.py +2 -2
  55. reflex/utils/telemetry.py +6 -4
  56. reflex/vars/base.py +15 -4
  57. reflex/vars/sequence.py +37 -0
  58. {reflex-0.6.7a2.dist-info → reflex-0.6.8.dist-info}/METADATA +2 -2
  59. {reflex-0.6.7a2.dist-info → reflex-0.6.8.dist-info}/RECORD +62 -61
  60. {reflex-0.6.7a2.dist-info → reflex-0.6.8.dist-info}/LICENSE +0 -0
  61. {reflex-0.6.7a2.dist-info → reflex-0.6.8.dist-info}/WHEEL +0 -0
  62. {reflex-0.6.7a2.dist-info → reflex-0.6.8.dist-info}/entry_points.txt +0 -0
reflex/state.py CHANGED
@@ -107,6 +107,7 @@ from reflex.utils.exceptions import (
107
107
  StateSchemaMismatchError,
108
108
  StateSerializationError,
109
109
  StateTooLargeError,
110
+ UnretrievableVarValueError,
110
111
  )
111
112
  from reflex.utils.exec import is_testing_env
112
113
  from reflex.utils.serializers import serializer
@@ -143,6 +144,9 @@ HANDLED_PICKLE_ERRORS = (
143
144
  ValueError,
144
145
  )
145
146
 
147
+ # For BaseState.get_var_value
148
+ VAR_TYPE = TypeVar("VAR_TYPE")
149
+
146
150
 
147
151
  def _no_chain_background_task(
148
152
  state_cls: Type["BaseState"], name: str, fn: Callable
@@ -1193,6 +1197,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1193
1197
  continue
1194
1198
  dynamic_vars[param] = DynamicRouteVar(
1195
1199
  fget=func,
1200
+ auto_deps=False,
1201
+ deps=["router"],
1196
1202
  cache=True,
1197
1203
  _js_expr=param,
1198
1204
  _var_data=VarData.from_state(cls),
@@ -1240,13 +1246,16 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1240
1246
  if not super().__getattribute__("__dict__"):
1241
1247
  return super().__getattribute__(name)
1242
1248
 
1243
- inherited_vars = {
1244
- **super().__getattribute__("inherited_vars"),
1245
- **super().__getattribute__("inherited_backend_vars"),
1246
- }
1249
+ # Fast path for dunder
1250
+ if name.startswith("__"):
1251
+ return super().__getattribute__(name)
1247
1252
 
1248
1253
  # For now, handle router_data updates as a special case.
1249
- if name in inherited_vars or name == constants.ROUTER_DATA:
1254
+ if (
1255
+ name == constants.ROUTER_DATA
1256
+ or name in super().__getattribute__("inherited_vars")
1257
+ or name in super().__getattribute__("inherited_backend_vars")
1258
+ ):
1250
1259
  parent_state = super().__getattribute__("parent_state")
1251
1260
  if parent_state is not None:
1252
1261
  return getattr(parent_state, name)
@@ -1301,8 +1310,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1301
1310
  value = value.__wrapped__
1302
1311
 
1303
1312
  # Set the var on the parent state.
1304
- inherited_vars = {**self.inherited_vars, **self.inherited_backend_vars}
1305
- if name in inherited_vars:
1313
+ if name in self.inherited_vars or name in self.inherited_backend_vars:
1306
1314
  setattr(self.parent_state, name, value)
1307
1315
  return
1308
1316
 
@@ -1596,6 +1604,42 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1596
1604
  # Slow case - fetch missing parent states from redis.
1597
1605
  return await self._get_state_from_redis(state_cls)
1598
1606
 
1607
+ async def get_var_value(self, var: Var[VAR_TYPE]) -> VAR_TYPE:
1608
+ """Get the value of an rx.Var from another state.
1609
+
1610
+ Args:
1611
+ var: The var to get the value for.
1612
+
1613
+ Returns:
1614
+ The value of the var.
1615
+
1616
+ Raises:
1617
+ UnretrievableVarValueError: If the var does not have a literal value
1618
+ or associated state.
1619
+ """
1620
+ # Oopsie case: you didn't give me a Var... so get what you give.
1621
+ if not isinstance(var, Var):
1622
+ return var # type: ignore
1623
+
1624
+ # Fast case: this is a literal var and the value is known.
1625
+ if hasattr(var, "_var_value"):
1626
+ return var._var_value
1627
+
1628
+ var_data = var._get_all_var_data()
1629
+ if var_data is None or not var_data.state:
1630
+ raise UnretrievableVarValueError(
1631
+ f"Unable to retrieve value for {var._js_expr}: not associated with any state."
1632
+ )
1633
+ # Fastish case: this var belongs to this state
1634
+ if var_data.state == self.get_full_name():
1635
+ return getattr(self, var_data.field_name)
1636
+
1637
+ # Slow case: this var belongs to another state
1638
+ other_state = await self.get_state(
1639
+ self._get_root_state().get_class_substate(var_data.state)
1640
+ )
1641
+ return getattr(other_state, var_data.field_name)
1642
+
1599
1643
  def _get_event_handler(
1600
1644
  self, event: Event
1601
1645
  ) -> tuple[BaseState | StateProxy, EventHandler]:
@@ -3645,6 +3689,9 @@ def get_state_manager() -> StateManager:
3645
3689
  class MutableProxy(wrapt.ObjectProxy):
3646
3690
  """A proxy for a mutable object that tracks changes."""
3647
3691
 
3692
+ # Hint for finding the base class of the proxy.
3693
+ __base_proxy__ = "MutableProxy"
3694
+
3648
3695
  # Methods on wrapped objects which should mark the state as dirty.
3649
3696
  __mark_dirty_attrs__ = {
3650
3697
  "add",
@@ -3687,6 +3734,39 @@ class MutableProxy(wrapt.ObjectProxy):
3687
3734
  BaseModelV1,
3688
3735
  )
3689
3736
 
3737
+ # Dynamically generated classes for tracking dataclass mutations.
3738
+ __dataclass_proxies__: Dict[type, type] = {}
3739
+
3740
+ def __new__(cls, wrapped: Any, *args, **kwargs) -> MutableProxy:
3741
+ """Create a proxy instance for a mutable object that tracks changes.
3742
+
3743
+ Args:
3744
+ wrapped: The object to proxy.
3745
+ *args: Other args passed to MutableProxy (ignored).
3746
+ **kwargs: Other kwargs passed to MutableProxy (ignored).
3747
+
3748
+ Returns:
3749
+ The proxy instance.
3750
+ """
3751
+ if dataclasses.is_dataclass(wrapped):
3752
+ wrapped_cls = type(wrapped)
3753
+ wrapper_cls_name = wrapped_cls.__name__ + cls.__name__
3754
+ # Find the associated class
3755
+ if wrapper_cls_name not in cls.__dataclass_proxies__:
3756
+ # Create a new class that has the __dataclass_fields__ defined
3757
+ cls.__dataclass_proxies__[wrapper_cls_name] = type(
3758
+ wrapper_cls_name,
3759
+ (cls,),
3760
+ {
3761
+ dataclasses._FIELDS: getattr( # pyright: ignore [reportGeneralTypeIssues]
3762
+ wrapped_cls,
3763
+ dataclasses._FIELDS, # pyright: ignore [reportGeneralTypeIssues]
3764
+ ),
3765
+ },
3766
+ )
3767
+ cls = cls.__dataclass_proxies__[wrapper_cls_name]
3768
+ return super().__new__(cls)
3769
+
3690
3770
  def __init__(self, wrapped: Any, state: BaseState, field_name: str):
3691
3771
  """Create a proxy for a mutable object that tracks changes.
3692
3772
 
@@ -3743,7 +3823,27 @@ class MutableProxy(wrapt.ObjectProxy):
3743
3823
  Returns:
3744
3824
  Whether the value is of a mutable type.
3745
3825
  """
3746
- return isinstance(value, cls.__mutable_types__)
3826
+ return isinstance(value, cls.__mutable_types__) or (
3827
+ dataclasses.is_dataclass(value) and not isinstance(value, Var)
3828
+ )
3829
+
3830
+ @staticmethod
3831
+ def _is_called_from_dataclasses_internal() -> bool:
3832
+ """Check if the current function is called from dataclasses helper.
3833
+
3834
+ Returns:
3835
+ Whether the current function is called from dataclasses internal code.
3836
+ """
3837
+ # Walk up the stack a bit to see if we are called from dataclasses
3838
+ # internal code, for example `asdict` or `astuple`.
3839
+ frame = inspect.currentframe()
3840
+ for _ in range(5):
3841
+ # Why not `inspect.stack()` -- this is much faster!
3842
+ if not (frame := frame and frame.f_back):
3843
+ break
3844
+ if inspect.getfile(frame) == dataclasses.__file__:
3845
+ return True
3846
+ return False
3747
3847
 
3748
3848
  def _wrap_recursive(self, value: Any) -> Any:
3749
3849
  """Wrap a value recursively if it is mutable.
@@ -3754,9 +3854,13 @@ class MutableProxy(wrapt.ObjectProxy):
3754
3854
  Returns:
3755
3855
  The wrapped value.
3756
3856
  """
3857
+ # When called from dataclasses internal code, return the unwrapped value
3858
+ if self._is_called_from_dataclasses_internal():
3859
+ return value
3757
3860
  # Recursively wrap mutable types, but do not re-wrap MutableProxy instances.
3758
3861
  if self._is_mutable_type(value) and not isinstance(value, MutableProxy):
3759
- return type(self)(
3862
+ base_cls = globals()[self.__base_proxy__]
3863
+ return base_cls(
3760
3864
  wrapped=value,
3761
3865
  state=self._self_state,
3762
3866
  field_name=self._self_field_name,
@@ -3964,6 +4068,9 @@ class ImmutableMutableProxy(MutableProxy):
3964
4068
  to modify the wrapped object when the StateProxy is immutable.
3965
4069
  """
3966
4070
 
4071
+ # Ensure that recursively wrapped proxies use ImmutableMutableProxy as base.
4072
+ __base_proxy__ = "ImmutableMutableProxy"
4073
+
3967
4074
  def _mark_dirty(
3968
4075
  self,
3969
4076
  wrapped=None,
reflex/testing.py CHANGED
@@ -52,6 +52,7 @@ from reflex.state import (
52
52
  StateManagerRedis,
53
53
  reload_state_module,
54
54
  )
55
+ from reflex.utils import console
55
56
 
56
57
  try:
57
58
  from selenium import webdriver # pyright: ignore [reportMissingImports]
@@ -385,7 +386,7 @@ class AppHarness:
385
386
  )
386
387
  if not line:
387
388
  break
388
- print(line) # for pytest diagnosis
389
+ print(line) # for pytest diagnosis #noqa: T201
389
390
  m = re.search(reflex.constants.Next.FRONTEND_LISTENING_REGEX, line)
390
391
  if m is not None:
391
392
  self.frontend_url = m.group(1)
@@ -403,11 +404,10 @@ class AppHarness:
403
404
  )
404
405
  # catch I/O operation on closed file.
405
406
  except ValueError as e:
406
- print(e)
407
+ console.error(str(e))
407
408
  break
408
409
  if not line:
409
410
  break
410
- print(line)
411
411
 
412
412
  self.frontend_output_thread = threading.Thread(target=consume_frontend_output)
413
413
  self.frontend_output_thread.start()
@@ -187,3 +187,7 @@ def raise_system_package_missing_error(package: str) -> NoReturn:
187
187
 
188
188
  class InvalidLockWarningThresholdError(ReflexError):
189
189
  """Raised when an invalid lock warning threshold is provided."""
190
+
191
+
192
+ class UnretrievableVarValueError(ReflexError):
193
+ """Raised when the value of a var is not retrievable."""
@@ -28,8 +28,8 @@ import typer
28
28
  from alembic.util.exc import CommandError
29
29
  from packaging import version
30
30
  from redis import Redis as RedisSync
31
- from redis import exceptions
32
31
  from redis.asyncio import Redis
32
+ from redis.exceptions import RedisError
33
33
 
34
34
  from reflex import constants, model
35
35
  from reflex.compiler import templates
@@ -109,7 +109,7 @@ def check_latest_package_version(package_name: str):
109
109
  console.warn(
110
110
  f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
111
111
  )
112
- # Check for depreacted python versions
112
+ # Check for deprecated python versions
113
113
  _python_version_check()
114
114
  except Exception:
115
115
  pass
@@ -333,10 +333,11 @@ def get_redis() -> Redis | None:
333
333
  Returns:
334
334
  The asynchronous redis client.
335
335
  """
336
- if isinstance((redis_url_or_options := parse_redis_url()), str):
337
- return Redis.from_url(redis_url_or_options)
338
- elif isinstance(redis_url_or_options, dict):
339
- return Redis(**redis_url_or_options)
336
+ if (redis_url := parse_redis_url()) is not None:
337
+ return Redis.from_url(
338
+ redis_url,
339
+ retry_on_error=[RedisError],
340
+ )
340
341
  return None
341
342
 
342
343
 
@@ -346,14 +347,15 @@ def get_redis_sync() -> RedisSync | None:
346
347
  Returns:
347
348
  The synchronous redis client.
348
349
  """
349
- if isinstance((redis_url_or_options := parse_redis_url()), str):
350
- return RedisSync.from_url(redis_url_or_options)
351
- elif isinstance(redis_url_or_options, dict):
352
- return RedisSync(**redis_url_or_options)
350
+ if (redis_url := parse_redis_url()) is not None:
351
+ return RedisSync.from_url(
352
+ redis_url,
353
+ retry_on_error=[RedisError],
354
+ )
353
355
  return None
354
356
 
355
357
 
356
- def parse_redis_url() -> str | dict | None:
358
+ def parse_redis_url() -> str | None:
357
359
  """Parse the REDIS_URL in config if applicable.
358
360
 
359
361
  Returns:
@@ -372,16 +374,13 @@ def parse_redis_url() -> str | dict | None:
372
374
  return config.redis_url
373
375
 
374
376
 
375
- async def get_redis_status() -> bool | None:
377
+ async def get_redis_status() -> dict[str, bool | None]:
376
378
  """Checks the status of the Redis connection.
377
379
 
378
380
  Attempts to connect to Redis and send a ping command to verify connectivity.
379
381
 
380
382
  Returns:
381
- bool or None: The status of the Redis connection:
382
- - True: Redis is accessible and responding.
383
- - False: Redis is not accessible due to a connection error.
384
- - None: Redis not used i.e redis_url is not set in rxconfig.
383
+ The status of the Redis connection.
385
384
  """
386
385
  try:
387
386
  status = True
@@ -390,10 +389,10 @@ async def get_redis_status() -> bool | None:
390
389
  redis_client.ping()
391
390
  else:
392
391
  status = None
393
- except exceptions.RedisError:
392
+ except RedisError:
394
393
  status = False
395
394
 
396
- return status
395
+ return {"redis": status}
397
396
 
398
397
 
399
398
  def validate_app_name(app_name: str | None = None) -> str:
@@ -594,7 +593,7 @@ def initialize_web_directory():
594
593
  """Initialize the web directory on reflex init."""
595
594
  console.log("Initializing the web directory.")
596
595
 
597
- # Re-use the hash if one is already created, so we don't over-write it when running reflex init
596
+ # Reuse the hash if one is already created, so we don't over-write it when running reflex init
598
597
  project_hash = get_project_hash()
599
598
 
600
599
  path_ops.cp(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
@@ -647,7 +646,7 @@ def initialize_bun_config():
647
646
  def init_reflex_json(project_hash: int | None):
648
647
  """Write the hash of the Reflex project to a REFLEX_JSON.
649
648
 
650
- Re-use the hash if one is already created, therefore do not
649
+ Reuse the hash if one is already created, therefore do not
651
650
  overwrite it every time we run the reflex init command
652
651
  .
653
652
 
@@ -1177,6 +1176,24 @@ def initialize_frontend_dependencies():
1177
1176
  initialize_web_directory()
1178
1177
 
1179
1178
 
1179
+ def check_db_used() -> bool:
1180
+ """Check if the database is used.
1181
+
1182
+ Returns:
1183
+ True if the database is used.
1184
+ """
1185
+ return bool(get_config().db_url)
1186
+
1187
+
1188
+ def check_redis_used() -> bool:
1189
+ """Check if Redis is used.
1190
+
1191
+ Returns:
1192
+ True if Redis is used.
1193
+ """
1194
+ return bool(get_config().redis_url)
1195
+
1196
+
1180
1197
  def check_db_initialized() -> bool:
1181
1198
  """Check if the database migrations are initialized.
1182
1199
 
reflex/utils/processes.py CHANGED
@@ -118,7 +118,7 @@ def handle_port(service_name: str, port: str, default_port: str) -> str:
118
118
  """Change port if the specified port is in use and is not explicitly specified as a CLI arg or config arg.
119
119
  otherwise tell the user the port is in use and exit the app.
120
120
 
121
- We make an assumption that when port is the default port,then it hasnt been explicitly set since its not straightforward
121
+ We make an assumption that when port is the default port,then it hasn't been explicitly set since its not straightforward
122
122
  to know whether a port was explicitly provided by the user unless its any other than the default.
123
123
 
124
124
  Args:
@@ -351,7 +351,7 @@ def atexit_handler():
351
351
 
352
352
  def get_command_with_loglevel(command: list[str]) -> list[str]:
353
353
  """Add the right loglevel flag to the designated command.
354
- npm uses --loglevel <level>, Bun doesnt use the --loglevel flag and
354
+ npm uses --loglevel <level>, Bun doesn't use the --loglevel flag and
355
355
  runs in debug mode by default.
356
356
 
357
357
  Args:
@@ -1023,7 +1023,7 @@ class InitStubGenerator(StubGenerator):
1023
1023
 
1024
1024
  class PyiGenerator:
1025
1025
  """A .pyi file generator that will scan all defined Component in Reflex and
1026
- generate the approriate stub.
1026
+ generate the appropriate stub.
1027
1027
  """
1028
1028
 
1029
1029
  modules: list = []
@@ -1202,4 +1202,4 @@ class PyiGenerator:
1202
1202
  or "Var[Template]" in line
1203
1203
  ):
1204
1204
  line = line.rstrip() + " # type: ignore\n"
1205
- print(line, end="")
1205
+ print(line, end="") # noqa: T201
reflex/utils/telemetry.py CHANGED
@@ -7,6 +7,7 @@ import dataclasses
7
7
  import multiprocessing
8
8
  import platform
9
9
  import warnings
10
+ from contextlib import suppress
10
11
 
11
12
  from reflex.config import environment
12
13
 
@@ -171,10 +172,11 @@ def _send(event, telemetry_enabled, **kwargs):
171
172
  if not telemetry_enabled:
172
173
  return False
173
174
 
174
- event_data = _prepare_event(event, **kwargs)
175
- if not event_data:
176
- return False
177
- return _send_event(event_data)
175
+ with suppress(Exception):
176
+ event_data = _prepare_event(event, **kwargs)
177
+ if not event_data:
178
+ return False
179
+ return _send_event(event_data)
178
180
 
179
181
 
180
182
  def send(event: str, telemetry_enabled: bool | None = None, **kwargs):
reflex/vars/base.py CHANGED
@@ -127,7 +127,7 @@ class VarData:
127
127
  state: str = "",
128
128
  field_name: str = "",
129
129
  imports: ImportDict | ParsedImportDict | None = None,
130
- hooks: dict[str, None] | None = None,
130
+ hooks: dict[str, VarData | None] | None = None,
131
131
  deps: list[Var] | None = None,
132
132
  position: Hooks.HookPosition | None = None,
133
133
  ):
@@ -194,7 +194,9 @@ class VarData:
194
194
  (var_data.state for var_data in all_var_datas if var_data.state), ""
195
195
  )
196
196
 
197
- hooks = {hook: None for var_data in all_var_datas for hook in var_data.hooks}
197
+ hooks: dict[str, VarData | None] = {
198
+ hook: None for var_data in all_var_datas for hook in var_data.hooks
199
+ }
198
200
 
199
201
  _imports = imports.merge_imports(
200
202
  *(var_data.imports for var_data in all_var_datas)
@@ -579,7 +581,7 @@ class Var(Generic[VAR_TYPE]):
579
581
 
580
582
  # Try to pull the imports and hooks from contained values.
581
583
  if not isinstance(value, str):
582
- return LiteralVar.create(value)
584
+ return LiteralVar.create(value, _var_data=_var_data)
583
585
 
584
586
  if _var_is_string is False or _var_is_local is True:
585
587
  return cls(
@@ -2276,7 +2278,7 @@ def computed_var(
2276
2278
  def computed_var(
2277
2279
  fget: Callable[[BASE_STATE], Any] | None = None,
2278
2280
  initial_value: Any | types.Unset = types.Unset(),
2279
- cache: bool = False,
2281
+ cache: Optional[bool] = None,
2280
2282
  deps: Optional[List[Union[str, Var]]] = None,
2281
2283
  auto_deps: bool = True,
2282
2284
  interval: Optional[Union[datetime.timedelta, int]] = None,
@@ -2302,6 +2304,15 @@ def computed_var(
2302
2304
  ValueError: If caching is disabled and an update interval is set.
2303
2305
  VarDependencyError: If user supplies dependencies without caching.
2304
2306
  """
2307
+ if cache is None:
2308
+ cache = False
2309
+ console.deprecate(
2310
+ "Default non-cached rx.var",
2311
+ "the default value will be `@rx.var(cache=True)` in a future release. "
2312
+ "To retain uncached var, explicitly pass `@rx.var(cache=False)`",
2313
+ deprecation_version="0.6.8",
2314
+ removal_version="0.7.0",
2315
+ )
2305
2316
  if cache is False and interval is not None:
2306
2317
  raise ValueError("Cannot set update interval without caching.")
2307
2318
 
reflex/vars/sequence.py CHANGED
@@ -271,6 +271,25 @@ class StringVar(Var[STRING_TYPE], python_types=str):
271
271
  raise_unsupported_operand_types("startswith", (type(self), type(prefix)))
272
272
  return string_starts_with_operation(self, prefix)
273
273
 
274
+ @overload
275
+ def endswith(self, suffix: StringVar | str) -> BooleanVar: ...
276
+
277
+ @overload
278
+ def endswith(self, suffix: NoReturn) -> NoReturn: ...
279
+
280
+ def endswith(self, suffix: Any) -> BooleanVar:
281
+ """Check if the string ends with a suffix.
282
+
283
+ Args:
284
+ suffix: The suffix.
285
+
286
+ Returns:
287
+ The string ends with operation.
288
+ """
289
+ if not isinstance(suffix, (StringVar, str)):
290
+ raise_unsupported_operand_types("endswith", (type(self), type(suffix)))
291
+ return string_ends_with_operation(self, suffix)
292
+
274
293
  @overload
275
294
  def __lt__(self, other: StringVar | str) -> BooleanVar: ...
276
295
 
@@ -501,6 +520,24 @@ def string_starts_with_operation(
501
520
  )
502
521
 
503
522
 
523
+ @var_operation
524
+ def string_ends_with_operation(
525
+ full_string: StringVar[Any], suffix: StringVar[Any] | str
526
+ ):
527
+ """Check if a string ends with a suffix.
528
+
529
+ Args:
530
+ full_string: The full string.
531
+ suffix: The suffix.
532
+
533
+ Returns:
534
+ Whether the string ends with the suffix.
535
+ """
536
+ return var_operation_return(
537
+ js_expression=f"{full_string}.endsWith({suffix})", var_type=bool
538
+ )
539
+
540
+
504
541
  @var_operation
505
542
  def string_item_operation(string: StringVar[Any], index: NumberVar | int):
506
543
  """Get an item from a string.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: reflex
3
- Version: 0.6.7a2
3
+ Version: 0.6.8
4
4
  Summary: Web apps in pure Python.
5
5
  Home-page: https://reflex.dev
6
6
  License: Apache-2.0
@@ -301,7 +301,7 @@ We welcome contributions of any size! Below are some good ways to get started in
301
301
  - **GitHub Discussions**: A great way to talk about features you want added or things that are confusing/need clarification.
302
302
  - **GitHub Issues**: [Issues](https://github.com/reflex-dev/reflex/issues) are an excellent way to report bugs. Additionally, you can try and solve an existing issue and submit a PR.
303
303
 
304
- We are actively looking for contributors, no matter your skill level or experience. To contribute check out [CONTIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
304
+ We are actively looking for contributors, no matter your skill level or experience. To contribute check out [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md)
305
305
 
306
306
 
307
307
  ## All Thanks To Our Contributors: