reflex 0.6.6.post3__py3-none-any.whl → 0.6.7a1__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 (141) hide show
  1. reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +5 -1
  2. reflex/.templates/web/utils/state.js +36 -28
  3. reflex/__init__.py +1 -1
  4. reflex/__init__.pyi +1 -0
  5. reflex/app.py +41 -16
  6. reflex/assets.py +2 -2
  7. reflex/base.py +8 -7
  8. reflex/compiler/templates.py +1 -0
  9. reflex/compiler/utils.py +2 -3
  10. reflex/components/base/bare.py +2 -2
  11. reflex/components/component.py +54 -29
  12. reflex/components/core/banner.py +2 -2
  13. reflex/components/core/banner.pyi +1 -1
  14. reflex/components/core/client_side_routing.py +2 -2
  15. reflex/components/core/client_side_routing.pyi +1 -1
  16. reflex/components/core/clipboard.py +11 -9
  17. reflex/components/core/clipboard.pyi +1 -1
  18. reflex/components/core/cond.py +3 -3
  19. reflex/components/core/foreach.py +1 -1
  20. reflex/components/core/html.pyi +1 -1
  21. reflex/components/core/upload.py +8 -8
  22. reflex/components/datadisplay/code.py +5 -5
  23. reflex/components/datadisplay/dataeditor.py +8 -28
  24. reflex/components/datadisplay/dataeditor.pyi +1 -1
  25. reflex/components/datadisplay/shiki_code_block.py +7 -7
  26. reflex/components/dynamic.py +2 -2
  27. reflex/components/el/elements/__init__.py +1 -1
  28. reflex/components/el/elements/__init__.pyi +1 -1
  29. reflex/components/el/elements/base.py +2 -2
  30. reflex/components/el/elements/base.pyi +1 -1
  31. reflex/components/el/elements/forms.py +40 -10
  32. reflex/components/el/elements/forms.pyi +17 -15
  33. reflex/components/el/elements/inline.py +1 -1
  34. reflex/components/el/elements/inline.pyi +28 -28
  35. reflex/components/el/elements/media.py +1 -4
  36. reflex/components/el/elements/media.pyi +25 -26
  37. reflex/components/el/elements/metadata.py +6 -6
  38. reflex/components/el/elements/metadata.pyi +4 -4
  39. reflex/components/el/elements/other.py +17 -9
  40. reflex/components/el/elements/other.pyi +7 -7
  41. reflex/components/el/elements/scripts.py +1 -2
  42. reflex/components/el/elements/scripts.pyi +3 -3
  43. reflex/components/el/elements/sectioning.py +16 -16
  44. reflex/components/el/elements/sectioning.pyi +15 -15
  45. reflex/components/el/elements/tables.py +1 -1
  46. reflex/components/el/elements/tables.pyi +10 -10
  47. reflex/components/el/elements/typography.py +1 -1
  48. reflex/components/el/elements/typography.pyi +15 -15
  49. reflex/components/markdown/markdown.py +3 -3
  50. reflex/components/next/image.py +1 -1
  51. reflex/components/next/image.pyi +1 -1
  52. reflex/components/plotly/plotly.py +2 -2
  53. reflex/components/radix/primitives/accordion.py +2 -1
  54. reflex/components/radix/primitives/form.pyi +3 -3
  55. reflex/components/radix/primitives/slider.py +1 -1
  56. reflex/components/radix/themes/base.py +4 -10
  57. reflex/components/radix/themes/color_mode.pyi +2 -2
  58. reflex/components/radix/themes/components/alert_dialog.pyi +1 -1
  59. reflex/components/radix/themes/components/badge.pyi +1 -1
  60. reflex/components/radix/themes/components/button.pyi +1 -1
  61. reflex/components/radix/themes/components/callout.pyi +5 -5
  62. reflex/components/radix/themes/components/card.pyi +1 -1
  63. reflex/components/radix/themes/components/checkbox.pyi +3 -3
  64. reflex/components/radix/themes/components/context_menu.py +11 -0
  65. reflex/components/radix/themes/components/context_menu.pyi +155 -0
  66. reflex/components/radix/themes/components/dialog.pyi +1 -1
  67. reflex/components/radix/themes/components/hover_card.pyi +1 -1
  68. reflex/components/radix/themes/components/icon_button.py +1 -1
  69. reflex/components/radix/themes/components/icon_button.pyi +1 -1
  70. reflex/components/radix/themes/components/inset.pyi +1 -1
  71. reflex/components/radix/themes/components/popover.pyi +1 -1
  72. reflex/components/radix/themes/components/radio_group.py +2 -4
  73. reflex/components/radix/themes/components/radio_group.pyi +1 -1
  74. reflex/components/radix/themes/components/select.pyi +3 -3
  75. reflex/components/radix/themes/components/slider.pyi +1 -1
  76. reflex/components/radix/themes/components/switch.pyi +1 -1
  77. reflex/components/radix/themes/components/table.pyi +7 -7
  78. reflex/components/radix/themes/components/tabs.pyi +2 -2
  79. reflex/components/radix/themes/components/text_area.py +3 -0
  80. reflex/components/radix/themes/components/text_area.pyi +3 -1
  81. reflex/components/radix/themes/components/text_field.py +16 -1
  82. reflex/components/radix/themes/components/text_field.pyi +105 -17
  83. reflex/components/radix/themes/layout/box.pyi +1 -1
  84. reflex/components/radix/themes/layout/center.pyi +1 -1
  85. reflex/components/radix/themes/layout/flex.pyi +1 -1
  86. reflex/components/radix/themes/layout/grid.pyi +1 -1
  87. reflex/components/radix/themes/layout/list.py +0 -4
  88. reflex/components/radix/themes/layout/list.pyi +3 -8
  89. reflex/components/radix/themes/layout/section.pyi +1 -1
  90. reflex/components/radix/themes/layout/spacer.pyi +1 -1
  91. reflex/components/radix/themes/layout/stack.pyi +3 -3
  92. reflex/components/radix/themes/typography/blockquote.pyi +1 -1
  93. reflex/components/radix/themes/typography/code.pyi +1 -1
  94. reflex/components/radix/themes/typography/heading.pyi +1 -1
  95. reflex/components/radix/themes/typography/link.py +5 -1
  96. reflex/components/radix/themes/typography/link.pyi +1 -1
  97. reflex/components/radix/themes/typography/text.pyi +7 -7
  98. reflex/components/recharts/cartesian.py +1 -1
  99. reflex/components/recharts/charts.py +4 -4
  100. reflex/components/recharts/polar.py +1 -1
  101. reflex/components/recharts/polar.pyi +1 -1
  102. reflex/components/sonner/toast.py +4 -7
  103. reflex/components/suneditor/editor.py +6 -6
  104. reflex/components/suneditor/editor.pyi +6 -6
  105. reflex/config.py +25 -10
  106. reflex/constants/compiler.py +6 -0
  107. reflex/constants/config.py +2 -0
  108. reflex/constants/custom_components.py +1 -1
  109. reflex/constants/route.py +1 -1
  110. reflex/custom_components/custom_components.py +21 -21
  111. reflex/event.py +57 -22
  112. reflex/experimental/client_state.py +2 -1
  113. reflex/experimental/layout.py +0 -6
  114. reflex/model.py +125 -9
  115. reflex/reflex.py +5 -6
  116. reflex/state.py +203 -88
  117. reflex/style.py +1 -4
  118. reflex/testing.py +10 -11
  119. reflex/utils/build.py +1 -1
  120. reflex/utils/console.py +75 -6
  121. reflex/utils/exceptions.py +12 -0
  122. reflex/utils/exec.py +10 -10
  123. reflex/utils/export.py +1 -2
  124. reflex/utils/format.py +11 -8
  125. reflex/utils/path_ops.py +2 -2
  126. reflex/utils/prerequisites.py +31 -28
  127. reflex/utils/processes.py +4 -4
  128. reflex/utils/pyi_generator.py +12 -11
  129. reflex/utils/types.py +6 -3
  130. reflex/vars/__init__.py +1 -0
  131. reflex/vars/base.py +75 -38
  132. reflex/vars/datetime.py +222 -0
  133. reflex/vars/function.py +3 -3
  134. reflex/vars/number.py +3 -3
  135. reflex/vars/object.py +5 -5
  136. reflex/vars/sequence.py +7 -7
  137. {reflex-0.6.6.post3.dist-info → reflex-0.6.7a1.dist-info}/METADATA +2 -2
  138. {reflex-0.6.6.post3.dist-info → reflex-0.6.7a1.dist-info}/RECORD +141 -140
  139. {reflex-0.6.6.post3.dist-info → reflex-0.6.7a1.dist-info}/LICENSE +0 -0
  140. {reflex-0.6.6.post3.dist-info → reflex-0.6.7a1.dist-info}/WHEEL +0 -0
  141. {reflex-0.6.6.post3.dist-info → reflex-0.6.7a1.dist-info}/entry_points.txt +0 -0
@@ -33,12 +33,6 @@ class Sidebar(Box, MemoizationLeaf):
33
33
  Returns:
34
34
  The sidebar component.
35
35
  """
36
- # props.setdefault("border_right", f"1px solid {color('accent', 12)}")
37
- # props.setdefault("background_color", color("accent", 1))
38
- # props.setdefault("width", "20vw")
39
- # props.setdefault("height", "100vh")
40
- # props.setdefault("position", "fixed")
41
-
42
36
  return super().create(
43
37
  Box.create(*children, **props), # sidebar for content
44
38
  Box.create(width=props.get("width")), # spacer for layout
reflex/model.py CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from collections import defaultdict
7
+ from contextlib import suppress
6
8
  from typing import Any, ClassVar, Optional, Type, Union
7
9
 
8
10
  import alembic.autogenerate
@@ -14,6 +16,7 @@ import alembic.script
14
16
  import alembic.util
15
17
  import sqlalchemy
16
18
  import sqlalchemy.exc
19
+ import sqlalchemy.ext.asyncio
17
20
  import sqlalchemy.orm
18
21
 
19
22
  from reflex.base import Base
@@ -21,6 +24,48 @@ from reflex.config import environment, get_config
21
24
  from reflex.utils import console
22
25
  from reflex.utils.compat import sqlmodel, sqlmodel_field_has_primary_key
23
26
 
27
+ _ENGINE: dict[str, sqlalchemy.engine.Engine] = {}
28
+ _ASYNC_ENGINE: dict[str, sqlalchemy.ext.asyncio.AsyncEngine] = {}
29
+ _AsyncSessionLocal: dict[str | None, sqlalchemy.ext.asyncio.async_sessionmaker] = {}
30
+
31
+ # Import AsyncSession _after_ reflex.utils.compat
32
+ from sqlmodel.ext.asyncio.session import AsyncSession # noqa: E402
33
+
34
+
35
+ def _safe_db_url_for_logging(url: str) -> str:
36
+ """Remove username and password from the database URL for logging.
37
+
38
+ Args:
39
+ url: The database URL.
40
+
41
+ Returns:
42
+ The database URL with the username and password removed.
43
+ """
44
+ return re.sub(r"://[^@]+@", "://<username>:<password>@", url)
45
+
46
+
47
+ def get_engine_args(url: str | None = None) -> dict[str, Any]:
48
+ """Get the database engine arguments.
49
+
50
+ Args:
51
+ url: The database url.
52
+
53
+ Returns:
54
+ The database engine arguments as a dict.
55
+ """
56
+ kwargs: dict[str, Any] = {
57
+ # Print the SQL queries if the log level is INFO or lower.
58
+ "echo": environment.SQLALCHEMY_ECHO.get(),
59
+ # Check connections before returning them.
60
+ "pool_pre_ping": environment.SQLALCHEMY_POOL_PRE_PING.get(),
61
+ }
62
+ conf = get_config()
63
+ url = url or conf.db_url
64
+ if url is not None and url.startswith("sqlite"):
65
+ # Needed for the admin dash on sqlite.
66
+ kwargs["connect_args"] = {"check_same_thread": False}
67
+ return kwargs
68
+
24
69
 
25
70
  def get_engine(url: str | None = None) -> sqlalchemy.engine.Engine:
26
71
  """Get the database engine.
@@ -38,15 +83,62 @@ def get_engine(url: str | None = None) -> sqlalchemy.engine.Engine:
38
83
  url = url or conf.db_url
39
84
  if url is None:
40
85
  raise ValueError("No database url configured")
86
+
87
+ global _ENGINE
88
+ if url in _ENGINE:
89
+ return _ENGINE[url]
90
+
41
91
  if not environment.ALEMBIC_CONFIG.get().exists():
42
92
  console.warn(
43
93
  "Database is not initialized, run [bold]reflex db init[/bold] first."
44
94
  )
45
- # Print the SQL queries if the log level is INFO or lower.
46
- echo_db_query = environment.SQLALCHEMY_ECHO.get()
47
- # Needed for the admin dash on sqlite.
48
- connect_args = {"check_same_thread": False} if url.startswith("sqlite") else {}
49
- return sqlmodel.create_engine(url, echo=echo_db_query, connect_args=connect_args)
95
+ _ENGINE[url] = sqlmodel.create_engine(
96
+ url,
97
+ **get_engine_args(url),
98
+ )
99
+ return _ENGINE[url]
100
+
101
+
102
+ def get_async_engine(url: str | None) -> sqlalchemy.ext.asyncio.AsyncEngine:
103
+ """Get the async database engine.
104
+
105
+ Args:
106
+ url: The database url.
107
+
108
+ Returns:
109
+ The async database engine.
110
+
111
+ Raises:
112
+ ValueError: If the async database url is None.
113
+ """
114
+ if url is None:
115
+ conf = get_config()
116
+ url = conf.async_db_url
117
+ if url is not None and conf.db_url is not None:
118
+ async_db_url_tail = url.partition("://")[2]
119
+ db_url_tail = conf.db_url.partition("://")[2]
120
+ if async_db_url_tail != db_url_tail:
121
+ console.warn(
122
+ f"async_db_url `{_safe_db_url_for_logging(url)}` "
123
+ "should reference the same database as "
124
+ f"db_url `{_safe_db_url_for_logging(conf.db_url)}`."
125
+ )
126
+ if url is None:
127
+ raise ValueError("No async database url configured")
128
+
129
+ global _ASYNC_ENGINE
130
+ if url in _ASYNC_ENGINE:
131
+ return _ASYNC_ENGINE[url]
132
+
133
+ if not environment.ALEMBIC_CONFIG.get().exists():
134
+ console.warn(
135
+ "Database is not initialized, run [bold]reflex db init[/bold] first."
136
+ )
137
+ _ASYNC_ENGINE[url] = sqlalchemy.ext.asyncio.create_async_engine(
138
+ url,
139
+ **get_engine_args(url),
140
+ )
141
+ return _ASYNC_ENGINE[url]
50
142
 
51
143
 
52
144
  async def get_db_status() -> bool:
@@ -199,11 +291,10 @@ class Model(Base, sqlmodel.SQLModel): # pyright: ignore [reportGeneralTypeIssue
199
291
  relationships = {}
200
292
  # SQLModel relationships do not appear in __fields__, but should be included if present.
201
293
  for name in self.__sqlmodel_relationships__:
202
- try:
294
+ with suppress(
295
+ sqlalchemy.orm.exc.DetachedInstanceError # This happens when the relationship was never loaded and the session is closed.
296
+ ):
203
297
  relationships[name] = self._dict_recursive(getattr(self, name))
204
- except sqlalchemy.orm.exc.DetachedInstanceError:
205
- # This happens when the relationship was never loaded and the session is closed.
206
- continue
207
298
  return {
208
299
  **base_fields,
209
300
  **relationships,
@@ -425,6 +516,31 @@ def session(url: str | None = None) -> sqlmodel.Session:
425
516
  return sqlmodel.Session(get_engine(url))
426
517
 
427
518
 
519
+ def asession(url: str | None = None) -> AsyncSession:
520
+ """Get an async sqlmodel session to interact with the database.
521
+
522
+ async with rx.asession() as asession:
523
+ ...
524
+
525
+ Most operations against the `asession` must be awaited.
526
+
527
+ Args:
528
+ url: The database url.
529
+
530
+ Returns:
531
+ An async database session.
532
+ """
533
+ global _AsyncSessionLocal
534
+ if url not in _AsyncSessionLocal:
535
+ _AsyncSessionLocal[url] = sqlalchemy.ext.asyncio.async_sessionmaker(
536
+ bind=get_async_engine(url),
537
+ class_=AsyncSession,
538
+ autocommit=False,
539
+ autoflush=False,
540
+ )
541
+ return _AsyncSessionLocal[url]()
542
+
543
+
428
544
  def sqla_session(url: str | None = None) -> sqlalchemy.orm.Session:
429
545
  """Get a bare sqlalchemy session to interact with the database.
430
546
 
reflex/reflex.py CHANGED
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import atexit
6
- import os
7
6
  from pathlib import Path
8
7
  from typing import List, Optional
9
8
 
@@ -298,7 +297,7 @@ def export(
298
297
  True, "--frontend-only", help="Export only frontend.", show_default=False
299
298
  ),
300
299
  zip_dest_dir: str = typer.Option(
301
- os.getcwd(),
300
+ str(Path.cwd()),
302
301
  help="The directory to export the zip files to.",
303
302
  show_default=False,
304
303
  ),
@@ -443,20 +442,20 @@ def deploy(
443
442
  hidden=True,
444
443
  ),
445
444
  regions: List[str] = typer.Option(
446
- list(),
445
+ [],
447
446
  "-r",
448
447
  "--region",
449
- help="The regions to deploy to. `reflex apps regions` For multiple envs, repeat this option, e.g. --region sjc --region iad",
448
+ help="The regions to deploy to. `reflex cloud regions` For multiple envs, repeat this option, e.g. --region sjc --region iad",
450
449
  ),
451
450
  envs: List[str] = typer.Option(
452
- list(),
451
+ [],
453
452
  "--env",
454
453
  help="The environment variables to set: <key>=<value>. For multiple envs, repeat this option, e.g. --env k1=v2 --env k2=v2.",
455
454
  ),
456
455
  vmtype: Optional[str] = typer.Option(
457
456
  None,
458
457
  "--vmtype",
459
- help="Vm type id. Run `reflex apps vmtypes` to get options.",
458
+ help="Vm type id. Run `reflex cloud vmtypes` to get options.",
460
459
  ),
461
460
  hostname: Optional[str] = typer.Option(
462
461
  None,