reflex 0.6.6.post2__py3-none-any.whl → 0.6.7__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.
- reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +5 -1
- reflex/.templates/web/utils/state.js +36 -28
- reflex/__init__.py +1 -1
- reflex/__init__.pyi +1 -0
- reflex/app.py +41 -16
- reflex/assets.py +2 -2
- reflex/base.py +8 -7
- reflex/compiler/templates.py +1 -0
- reflex/compiler/utils.py +2 -3
- reflex/components/base/bare.py +2 -2
- reflex/components/component.py +54 -29
- reflex/components/core/banner.py +2 -2
- reflex/components/core/banner.pyi +1 -1
- reflex/components/core/client_side_routing.py +2 -2
- reflex/components/core/client_side_routing.pyi +1 -1
- reflex/components/core/clipboard.py +11 -9
- reflex/components/core/clipboard.pyi +1 -1
- reflex/components/core/cond.py +3 -3
- reflex/components/core/foreach.py +1 -1
- reflex/components/core/html.pyi +1 -1
- reflex/components/core/upload.py +8 -8
- reflex/components/datadisplay/code.py +5 -5
- reflex/components/datadisplay/dataeditor.py +8 -28
- reflex/components/datadisplay/dataeditor.pyi +1 -1
- reflex/components/datadisplay/shiki_code_block.py +7 -7
- reflex/components/dynamic.py +2 -2
- reflex/components/el/elements/__init__.py +1 -1
- reflex/components/el/elements/__init__.pyi +1 -1
- reflex/components/el/elements/base.py +2 -2
- reflex/components/el/elements/base.pyi +1 -1
- reflex/components/el/elements/forms.py +40 -10
- reflex/components/el/elements/forms.pyi +17 -15
- reflex/components/el/elements/inline.py +1 -1
- reflex/components/el/elements/inline.pyi +28 -28
- reflex/components/el/elements/media.py +1 -4
- reflex/components/el/elements/media.pyi +25 -26
- reflex/components/el/elements/metadata.py +6 -6
- reflex/components/el/elements/metadata.pyi +4 -4
- reflex/components/el/elements/other.py +17 -9
- reflex/components/el/elements/other.pyi +7 -7
- reflex/components/el/elements/scripts.py +1 -2
- reflex/components/el/elements/scripts.pyi +3 -3
- reflex/components/el/elements/sectioning.py +16 -16
- reflex/components/el/elements/sectioning.pyi +15 -15
- reflex/components/el/elements/tables.py +1 -1
- reflex/components/el/elements/tables.pyi +10 -10
- reflex/components/el/elements/typography.py +1 -1
- reflex/components/el/elements/typography.pyi +15 -15
- reflex/components/markdown/markdown.py +3 -3
- reflex/components/next/image.py +1 -1
- reflex/components/next/image.pyi +1 -1
- reflex/components/plotly/plotly.py +2 -2
- reflex/components/radix/primitives/accordion.py +2 -1
- reflex/components/radix/primitives/form.pyi +3 -3
- reflex/components/radix/primitives/slider.py +1 -1
- reflex/components/radix/themes/base.py +4 -10
- reflex/components/radix/themes/color_mode.pyi +2 -2
- reflex/components/radix/themes/components/alert_dialog.pyi +1 -1
- reflex/components/radix/themes/components/badge.pyi +1 -1
- reflex/components/radix/themes/components/button.pyi +1 -1
- reflex/components/radix/themes/components/callout.pyi +5 -5
- reflex/components/radix/themes/components/card.pyi +1 -1
- reflex/components/radix/themes/components/checkbox.pyi +3 -3
- reflex/components/radix/themes/components/context_menu.py +11 -0
- reflex/components/radix/themes/components/context_menu.pyi +155 -0
- reflex/components/radix/themes/components/dialog.pyi +1 -1
- reflex/components/radix/themes/components/hover_card.pyi +1 -1
- reflex/components/radix/themes/components/icon_button.py +1 -1
- reflex/components/radix/themes/components/icon_button.pyi +1 -1
- reflex/components/radix/themes/components/inset.pyi +1 -1
- reflex/components/radix/themes/components/popover.pyi +1 -1
- reflex/components/radix/themes/components/radio_group.py +2 -4
- reflex/components/radix/themes/components/radio_group.pyi +1 -1
- reflex/components/radix/themes/components/select.pyi +3 -3
- reflex/components/radix/themes/components/slider.pyi +1 -1
- reflex/components/radix/themes/components/switch.pyi +1 -1
- reflex/components/radix/themes/components/table.pyi +7 -7
- reflex/components/radix/themes/components/tabs.pyi +2 -2
- reflex/components/radix/themes/components/text_area.py +3 -0
- reflex/components/radix/themes/components/text_area.pyi +3 -1
- reflex/components/radix/themes/components/text_field.py +16 -1
- reflex/components/radix/themes/components/text_field.pyi +105 -17
- reflex/components/radix/themes/layout/box.pyi +1 -1
- reflex/components/radix/themes/layout/center.pyi +1 -1
- reflex/components/radix/themes/layout/flex.pyi +1 -1
- reflex/components/radix/themes/layout/grid.pyi +1 -1
- reflex/components/radix/themes/layout/list.py +0 -4
- reflex/components/radix/themes/layout/list.pyi +3 -8
- reflex/components/radix/themes/layout/section.pyi +1 -1
- reflex/components/radix/themes/layout/spacer.pyi +1 -1
- reflex/components/radix/themes/layout/stack.pyi +3 -3
- reflex/components/radix/themes/typography/blockquote.pyi +1 -1
- reflex/components/radix/themes/typography/code.pyi +1 -1
- reflex/components/radix/themes/typography/heading.pyi +1 -1
- reflex/components/radix/themes/typography/link.py +5 -1
- reflex/components/radix/themes/typography/link.pyi +1 -1
- reflex/components/radix/themes/typography/text.pyi +7 -7
- reflex/components/recharts/cartesian.py +1 -1
- reflex/components/recharts/charts.py +4 -4
- reflex/components/recharts/polar.py +1 -1
- reflex/components/recharts/polar.pyi +1 -1
- reflex/components/sonner/toast.py +4 -7
- reflex/components/suneditor/editor.py +6 -6
- reflex/components/suneditor/editor.pyi +6 -6
- reflex/config.py +25 -10
- reflex/constants/compiler.py +6 -0
- reflex/constants/config.py +2 -0
- reflex/constants/custom_components.py +1 -1
- reflex/constants/route.py +1 -1
- reflex/custom_components/custom_components.py +21 -21
- reflex/event.py +57 -22
- reflex/experimental/client_state.py +2 -1
- reflex/experimental/layout.py +0 -6
- reflex/model.py +125 -9
- reflex/reflex.py +12 -8
- reflex/state.py +200 -88
- reflex/style.py +1 -4
- reflex/testing.py +10 -11
- reflex/utils/build.py +1 -1
- reflex/utils/console.py +75 -6
- reflex/utils/exceptions.py +12 -0
- reflex/utils/exec.py +10 -10
- reflex/utils/export.py +1 -2
- reflex/utils/format.py +11 -8
- reflex/utils/path_ops.py +2 -2
- reflex/utils/prerequisites.py +31 -28
- reflex/utils/processes.py +4 -4
- reflex/utils/pyi_generator.py +12 -11
- reflex/utils/types.py +6 -3
- reflex/vars/__init__.py +1 -0
- reflex/vars/base.py +75 -38
- reflex/vars/datetime.py +222 -0
- reflex/vars/function.py +3 -3
- reflex/vars/number.py +3 -3
- reflex/vars/object.py +5 -5
- reflex/vars/sequence.py +7 -7
- {reflex-0.6.6.post2.dist-info → reflex-0.6.7.dist-info}/METADATA +2 -2
- {reflex-0.6.6.post2.dist-info → reflex-0.6.7.dist-info}/RECORD +141 -140
- {reflex-0.6.6.post2.dist-info → reflex-0.6.7.dist-info}/LICENSE +0 -0
- {reflex-0.6.6.post2.dist-info → reflex-0.6.7.dist-info}/WHEEL +0 -0
- {reflex-0.6.6.post2.dist-info → reflex-0.6.7.dist-info}/entry_points.txt +0 -0
reflex/experimental/layout.py
CHANGED
|
@@ -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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
445
|
+
[],
|
|
447
446
|
"-r",
|
|
448
447
|
"--region",
|
|
449
|
-
help="The regions to deploy to. `reflex
|
|
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
|
-
|
|
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
|
|
458
|
+
help="Vm type id. Run `reflex cloud vmtypes` to get options.",
|
|
460
459
|
),
|
|
461
460
|
hostname: Optional[str] = typer.Option(
|
|
462
461
|
None,
|
|
@@ -497,8 +496,13 @@ def deploy(
|
|
|
497
496
|
|
|
498
497
|
# Set the log level.
|
|
499
498
|
console.set_log_level(loglevel)
|
|
500
|
-
|
|
501
|
-
|
|
499
|
+
|
|
500
|
+
if not token:
|
|
501
|
+
# make sure user is logged in.
|
|
502
|
+
if interactive:
|
|
503
|
+
hosting_cli.login()
|
|
504
|
+
else:
|
|
505
|
+
raise SystemExit("Token is required for non-interactive mode.")
|
|
502
506
|
|
|
503
507
|
# Only check requirements if interactive.
|
|
504
508
|
# There is user interaction for requirements update.
|