reflex 0.7.0a4__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.
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -1
- reflex/__init__.py +1 -0
- reflex/__init__.pyi +1 -0
- reflex/app.py +251 -68
- reflex/base.py +4 -10
- reflex/compiler/compiler.py +46 -12
- reflex/compiler/templates.py +1 -2
- reflex/compiler/utils.py +23 -14
- reflex/components/base/bare.py +109 -16
- reflex/components/component.py +179 -124
- reflex/components/core/__init__.py +1 -0
- reflex/components/core/__init__.pyi +1 -0
- reflex/components/core/auto_scroll.py +111 -0
- reflex/components/core/auto_scroll.pyi +284 -0
- reflex/components/core/banner.py +35 -5
- reflex/components/core/banner.pyi +398 -36
- reflex/components/core/breakpoints.py +1 -1
- reflex/components/core/cond.py +0 -8
- reflex/components/core/foreach.py +12 -2
- reflex/components/core/html.pyi +200 -19
- reflex/components/core/match.py +4 -4
- reflex/components/core/sticky.py +4 -30
- reflex/components/core/sticky.pyi +874 -90
- reflex/components/core/upload.py +3 -5
- reflex/components/core/upload.pyi +2 -4
- reflex/components/datadisplay/code.py +36 -10
- reflex/components/datadisplay/code.pyi +1 -1
- reflex/components/datadisplay/dataeditor.py +1 -3
- reflex/components/datadisplay/dataeditor.pyi +1 -3
- reflex/components/el/elements/base.py +95 -17
- reflex/components/el/elements/base.pyi +278 -19
- reflex/components/el/elements/forms.py +124 -102
- reflex/components/el/elements/forms.pyi +2787 -365
- reflex/components/el/elements/inline.py +24 -15
- reflex/components/el/elements/inline.pyi +5655 -546
- reflex/components/el/elements/media.py +79 -95
- reflex/components/el/elements/media.pyi +5167 -565
- reflex/components/el/elements/metadata.py +19 -17
- reflex/components/el/elements/metadata.pyi +841 -89
- reflex/components/el/elements/other.py +3 -5
- reflex/components/el/elements/other.pyi +1404 -137
- reflex/components/el/elements/scripts.py +10 -13
- reflex/components/el/elements/scripts.pyi +634 -65
- reflex/components/el/elements/sectioning.pyi +3001 -286
- reflex/components/el/elements/tables.py +14 -35
- reflex/components/el/elements/tables.pyi +2029 -218
- reflex/components/el/elements/typography.py +10 -13
- reflex/components/el/elements/typography.pyi +3014 -297
- reflex/components/lucide/icon.py +22 -6
- reflex/components/markdown/markdown.py +30 -10
- reflex/components/markdown/markdown.pyi +3 -2
- reflex/components/plotly/plotly.py +1 -3
- reflex/components/plotly/plotly.pyi +1 -3
- reflex/components/radix/primitives/form.pyi +624 -93
- reflex/components/radix/themes/color_mode.py +1 -1
- reflex/components/radix/themes/color_mode.pyi +213 -31
- reflex/components/radix/themes/components/alert_dialog.pyi +199 -18
- reflex/components/radix/themes/components/badge.pyi +199 -18
- reflex/components/radix/themes/components/button.pyi +213 -31
- reflex/components/radix/themes/components/callout.pyi +1000 -95
- reflex/components/radix/themes/components/card.pyi +199 -18
- reflex/components/radix/themes/components/context_menu.py +79 -1
- reflex/components/radix/themes/components/context_menu.pyi +320 -1
- reflex/components/radix/themes/components/dialog.pyi +199 -18
- reflex/components/radix/themes/components/hover_card.pyi +199 -18
- reflex/components/radix/themes/components/icon_button.pyi +213 -31
- reflex/components/radix/themes/components/inset.pyi +199 -18
- reflex/components/radix/themes/components/popover.pyi +199 -18
- reflex/components/radix/themes/components/table.pyi +1437 -154
- reflex/components/radix/themes/components/text_area.py +2 -2
- reflex/components/radix/themes/components/text_area.pyi +201 -20
- reflex/components/radix/themes/components/text_field.py +1 -1
- reflex/components/radix/themes/components/text_field.pyi +444 -88
- reflex/components/radix/themes/layout/box.pyi +200 -19
- reflex/components/radix/themes/layout/center.pyi +199 -18
- reflex/components/radix/themes/layout/container.pyi +199 -18
- reflex/components/radix/themes/layout/flex.pyi +199 -18
- reflex/components/radix/themes/layout/grid.pyi +199 -18
- reflex/components/radix/themes/layout/list.pyi +604 -57
- reflex/components/radix/themes/layout/section.pyi +199 -18
- reflex/components/radix/themes/layout/spacer.pyi +199 -18
- reflex/components/radix/themes/layout/stack.pyi +597 -54
- reflex/components/radix/themes/typography/blockquote.pyi +200 -19
- reflex/components/radix/themes/typography/code.pyi +199 -18
- reflex/components/radix/themes/typography/heading.pyi +199 -18
- reflex/components/radix/themes/typography/link.pyi +238 -28
- reflex/components/radix/themes/typography/text.pyi +1394 -127
- reflex/components/react_player/react_player.py +1 -1
- reflex/components/react_player/react_player.pyi +1 -3
- reflex/components/sonner/toast.py +19 -1
- reflex/components/sonner/toast.pyi +10 -1
- reflex/components/tags/iter_tag.py +4 -0
- reflex/components/tags/tag.py +3 -3
- reflex/config.py +187 -28
- reflex/constants/__init__.py +2 -0
- reflex/constants/base.py +6 -0
- reflex/constants/compiler.py +9 -0
- reflex/constants/event.py +1 -0
- reflex/constants/installer.py +4 -5
- reflex/constants/utils.py +1 -3
- reflex/event.py +7 -16
- reflex/experimental/layout.pyi +597 -54
- reflex/py.typed +0 -0
- reflex/reflex.py +44 -48
- reflex/state.py +49 -44
- reflex/style.py +6 -4
- reflex/testing.py +2 -0
- reflex/utils/build.py +12 -0
- reflex/utils/console.py +4 -0
- reflex/utils/decorator.py +25 -0
- reflex/utils/exec.py +92 -34
- reflex/utils/format.py +35 -6
- reflex/utils/path_ops.py +32 -1
- reflex/utils/prerequisites.py +54 -10
- reflex/utils/processes.py +12 -13
- reflex/utils/serializers.py +20 -43
- reflex/utils/telemetry.py +4 -15
- reflex/utils/types.py +36 -66
- reflex/vars/base.py +53 -76
- reflex/vars/function.py +17 -5
- reflex/vars/number.py +1 -1
- reflex/vars/sequence.py +80 -4
- {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/METADATA +4 -5
- {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/RECORD +127 -123
- {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/LICENSE +0 -0
- {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/WHEEL +0 -0
- {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/entry_points.txt +0 -0
reflex/py.typed
ADDED
|
File without changes
|
reflex/reflex.py
CHANGED
|
@@ -20,13 +20,8 @@ from reflex.utils import console, telemetry
|
|
|
20
20
|
typer.core.rich = None # pyright: ignore [reportPrivateImportUsage]
|
|
21
21
|
|
|
22
22
|
# Create the app.
|
|
23
|
-
|
|
24
|
-
cli = typer.Typer(add_completion=False, pretty_exceptions_enable=False)
|
|
25
|
-
except TypeError:
|
|
26
|
-
# Fallback for older typer versions.
|
|
27
|
-
cli = typer.Typer(add_completion=False)
|
|
23
|
+
cli = typer.Typer(add_completion=False, pretty_exceptions_enable=False)
|
|
28
24
|
|
|
29
|
-
SHOW_BUILT_WITH_REFLEX_INFO = "https://reflex.dev/docs/hosting/reflex-branding/"
|
|
30
25
|
|
|
31
26
|
# Get the config.
|
|
32
27
|
config = get_config()
|
|
@@ -127,8 +122,8 @@ def _run(
|
|
|
127
122
|
env: constants.Env = constants.Env.DEV,
|
|
128
123
|
frontend: bool = True,
|
|
129
124
|
backend: bool = True,
|
|
130
|
-
frontend_port: int =
|
|
131
|
-
backend_port: int =
|
|
125
|
+
frontend_port: int | None = None,
|
|
126
|
+
backend_port: int | None = None,
|
|
132
127
|
backend_host: str = config.backend_host,
|
|
133
128
|
loglevel: constants.LogLevel = config.loglevel,
|
|
134
129
|
):
|
|
@@ -145,10 +140,7 @@ def _run(
|
|
|
145
140
|
exec.output_system_info()
|
|
146
141
|
|
|
147
142
|
# If no --frontend-only and no --backend-only, then turn on frontend and backend both
|
|
148
|
-
|
|
149
|
-
frontend = True
|
|
150
|
-
backend = True
|
|
151
|
-
|
|
143
|
+
frontend, backend = prerequisites.check_running_mode(frontend, backend)
|
|
152
144
|
if not frontend and backend:
|
|
153
145
|
_skip_compile()
|
|
154
146
|
|
|
@@ -161,17 +153,28 @@ def _run(
|
|
|
161
153
|
|
|
162
154
|
# Find the next available open port if applicable.
|
|
163
155
|
if frontend:
|
|
156
|
+
auto_increment_frontend = not bool(frontend_port or config.frontend_port)
|
|
164
157
|
frontend_port = processes.handle_port(
|
|
165
158
|
"frontend",
|
|
166
|
-
|
|
167
|
-
|
|
159
|
+
(
|
|
160
|
+
frontend_port
|
|
161
|
+
or config.frontend_port
|
|
162
|
+
or constants.DefaultPorts.FRONTEND_PORT
|
|
163
|
+
),
|
|
164
|
+
auto_increment=auto_increment_frontend,
|
|
168
165
|
)
|
|
169
166
|
|
|
170
167
|
if backend:
|
|
168
|
+
auto_increment_backend = not bool(backend_port or config.backend_port)
|
|
169
|
+
|
|
171
170
|
backend_port = processes.handle_port(
|
|
172
171
|
"backend",
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
(
|
|
173
|
+
backend_port
|
|
174
|
+
or config.backend_port
|
|
175
|
+
or constants.DefaultPorts.BACKEND_PORT
|
|
176
|
+
),
|
|
177
|
+
auto_increment=auto_increment_backend,
|
|
175
178
|
)
|
|
176
179
|
|
|
177
180
|
# Apply the new ports to the config.
|
|
@@ -188,15 +191,6 @@ def _run(
|
|
|
188
191
|
prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME)
|
|
189
192
|
|
|
190
193
|
if frontend:
|
|
191
|
-
if not config.show_built_with_reflex:
|
|
192
|
-
# The sticky badge may be disabled at runtime for team/enterprise tiers.
|
|
193
|
-
prerequisites.check_config_option_in_tier(
|
|
194
|
-
option_name="show_built_with_reflex",
|
|
195
|
-
allowed_tiers=["team", "enterprise"],
|
|
196
|
-
fallback_value=True,
|
|
197
|
-
help_link=SHOW_BUILT_WITH_REFLEX_INFO,
|
|
198
|
-
)
|
|
199
|
-
|
|
200
194
|
# Get the app module.
|
|
201
195
|
prerequisites.get_compiled_app()
|
|
202
196
|
|
|
@@ -249,7 +243,7 @@ def _run(
|
|
|
249
243
|
# Start the frontend and backend.
|
|
250
244
|
with processes.run_concurrently_context(*commands):
|
|
251
245
|
# In dev mode, run the backend on the main thread.
|
|
252
|
-
if backend and env == constants.Env.DEV:
|
|
246
|
+
if backend and backend_port and env == constants.Env.DEV:
|
|
253
247
|
backend_cmd(
|
|
254
248
|
backend_host, int(backend_port), loglevel.subprocess_level(), frontend
|
|
255
249
|
)
|
|
@@ -278,10 +272,14 @@ def run(
|
|
|
278
272
|
envvar=environment.REFLEX_BACKEND_ONLY.name,
|
|
279
273
|
),
|
|
280
274
|
frontend_port: int = typer.Option(
|
|
281
|
-
config.frontend_port,
|
|
275
|
+
config.frontend_port,
|
|
276
|
+
help="Specify a different frontend port.",
|
|
277
|
+
envvar=environment.REFLEX_FRONTEND_PORT.name,
|
|
282
278
|
),
|
|
283
279
|
backend_port: int = typer.Option(
|
|
284
|
-
config.backend_port,
|
|
280
|
+
config.backend_port,
|
|
281
|
+
help="Specify a different backend port.",
|
|
282
|
+
envvar=environment.REFLEX_BACKEND_PORT.name,
|
|
285
283
|
),
|
|
286
284
|
backend_host: str = typer.Option(
|
|
287
285
|
config.backend_host, help="Specify the backend host."
|
|
@@ -294,6 +292,8 @@ def run(
|
|
|
294
292
|
if frontend and backend:
|
|
295
293
|
console.error("Cannot use both --frontend-only and --backend-only options.")
|
|
296
294
|
raise typer.Exit(1)
|
|
295
|
+
|
|
296
|
+
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.RUN)
|
|
297
297
|
environment.REFLEX_BACKEND_ONLY.set(backend)
|
|
298
298
|
environment.REFLEX_FRONTEND_ONLY.set(frontend)
|
|
299
299
|
|
|
@@ -306,10 +306,18 @@ def export(
|
|
|
306
306
|
True, "--no-zip", help="Disable zip for backend and frontend exports."
|
|
307
307
|
),
|
|
308
308
|
frontend: bool = typer.Option(
|
|
309
|
-
|
|
309
|
+
False,
|
|
310
|
+
"--frontend-only",
|
|
311
|
+
help="Export only frontend.",
|
|
312
|
+
show_default=False,
|
|
313
|
+
envvar=environment.REFLEX_FRONTEND_ONLY.name,
|
|
310
314
|
),
|
|
311
315
|
backend: bool = typer.Option(
|
|
312
|
-
|
|
316
|
+
False,
|
|
317
|
+
"--backend-only",
|
|
318
|
+
help="Export only backend.",
|
|
319
|
+
show_default=False,
|
|
320
|
+
envvar=environment.REFLEX_BACKEND_ONLY.name,
|
|
313
321
|
),
|
|
314
322
|
zip_dest_dir: str = typer.Option(
|
|
315
323
|
str(Path.cwd()),
|
|
@@ -332,17 +340,12 @@ def export(
|
|
|
332
340
|
from reflex.utils import export as export_utils
|
|
333
341
|
from reflex.utils import prerequisites
|
|
334
342
|
|
|
335
|
-
|
|
336
|
-
_init(name=config.app_name, loglevel=loglevel)
|
|
343
|
+
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.EXPORT)
|
|
337
344
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
allowed_tiers=["team", "enterprise"],
|
|
343
|
-
fallback_value=False,
|
|
344
|
-
help_link=SHOW_BUILT_WITH_REFLEX_INFO,
|
|
345
|
-
)
|
|
345
|
+
frontend, backend = prerequisites.check_running_mode(frontend, backend)
|
|
346
|
+
|
|
347
|
+
if prerequisites.needs_reinit(frontend=frontend or not backend):
|
|
348
|
+
_init(name=config.app_name, loglevel=loglevel)
|
|
346
349
|
|
|
347
350
|
export_utils.export(
|
|
348
351
|
zipping=zipping,
|
|
@@ -538,14 +541,7 @@ def deploy(
|
|
|
538
541
|
|
|
539
542
|
check_version()
|
|
540
543
|
|
|
541
|
-
|
|
542
|
-
# The sticky badge may be disabled on deploy for pro/team/enterprise tiers.
|
|
543
|
-
prerequisites.check_config_option_in_tier(
|
|
544
|
-
option_name="show_built_with_reflex",
|
|
545
|
-
allowed_tiers=["pro", "team", "enterprise"],
|
|
546
|
-
fallback_value=True,
|
|
547
|
-
help_link=SHOW_BUILT_WITH_REFLEX_INFO,
|
|
548
|
-
)
|
|
544
|
+
environment.REFLEX_COMPILE_CONTEXT.set(constants.CompileContext.DEPLOY)
|
|
549
545
|
|
|
550
546
|
# Set the log level.
|
|
551
547
|
console.set_log_level(loglevel)
|
reflex/state.py
CHANGED
|
@@ -40,50 +40,22 @@ from typing import (
|
|
|
40
40
|
get_type_hints,
|
|
41
41
|
)
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
from sqlalchemy.orm import DeclarativeBase
|
|
45
|
-
from typing_extensions import Self
|
|
46
|
-
|
|
47
|
-
from reflex import event
|
|
48
|
-
from reflex.config import PerformanceMode, get_config
|
|
49
|
-
from reflex.istate.data import RouterData
|
|
50
|
-
from reflex.istate.storage import ClientStorageBase
|
|
51
|
-
from reflex.model import Model
|
|
52
|
-
from reflex.vars.base import (
|
|
53
|
-
ComputedVar,
|
|
54
|
-
DynamicRouteVar,
|
|
55
|
-
Var,
|
|
56
|
-
computed_var,
|
|
57
|
-
dispatch,
|
|
58
|
-
get_unique_variable_name,
|
|
59
|
-
is_computed_var,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
try:
|
|
63
|
-
import pydantic.v1 as pydantic
|
|
64
|
-
except ModuleNotFoundError:
|
|
65
|
-
import pydantic
|
|
66
|
-
|
|
67
|
-
from pydantic import BaseModel as BaseModelV2
|
|
68
|
-
|
|
69
|
-
try:
|
|
70
|
-
from pydantic.v1 import BaseModel as BaseModelV1
|
|
71
|
-
except ModuleNotFoundError:
|
|
72
|
-
BaseModelV1 = BaseModelV2
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
from pydantic.v1 import validator
|
|
76
|
-
except ModuleNotFoundError:
|
|
77
|
-
from pydantic import validator
|
|
78
|
-
|
|
43
|
+
import pydantic.v1 as pydantic
|
|
79
44
|
import wrapt
|
|
45
|
+
from pydantic import BaseModel as BaseModelV2
|
|
46
|
+
from pydantic.v1 import BaseModel as BaseModelV1
|
|
47
|
+
from pydantic.v1 import validator
|
|
48
|
+
from pydantic.v1.fields import ModelField
|
|
80
49
|
from redis.asyncio import Redis
|
|
50
|
+
from redis.asyncio.client import PubSub
|
|
81
51
|
from redis.exceptions import ResponseError
|
|
52
|
+
from sqlalchemy.orm import DeclarativeBase
|
|
53
|
+
from typing_extensions import Self
|
|
82
54
|
|
|
83
55
|
import reflex.istate.dynamic
|
|
84
|
-
from reflex import constants
|
|
56
|
+
from reflex import constants, event
|
|
85
57
|
from reflex.base import Base
|
|
86
|
-
from reflex.config import environment
|
|
58
|
+
from reflex.config import PerformanceMode, environment, get_config
|
|
87
59
|
from reflex.event import (
|
|
88
60
|
BACKGROUND_TASK_MARKER,
|
|
89
61
|
Event,
|
|
@@ -91,6 +63,9 @@ from reflex.event import (
|
|
|
91
63
|
EventSpec,
|
|
92
64
|
fix_events,
|
|
93
65
|
)
|
|
66
|
+
from reflex.istate.data import RouterData
|
|
67
|
+
from reflex.istate.storage import ClientStorageBase
|
|
68
|
+
from reflex.model import Model
|
|
94
69
|
from reflex.utils import console, format, path_ops, prerequisites, types
|
|
95
70
|
from reflex.utils.exceptions import (
|
|
96
71
|
ComputedVarShadowsBaseVarsError,
|
|
@@ -121,6 +96,15 @@ from reflex.utils.types import (
|
|
|
121
96
|
value_inside_optional,
|
|
122
97
|
)
|
|
123
98
|
from reflex.vars import VarData
|
|
99
|
+
from reflex.vars.base import (
|
|
100
|
+
ComputedVar,
|
|
101
|
+
DynamicRouteVar,
|
|
102
|
+
Var,
|
|
103
|
+
computed_var,
|
|
104
|
+
dispatch,
|
|
105
|
+
get_unique_variable_name,
|
|
106
|
+
is_computed_var,
|
|
107
|
+
)
|
|
124
108
|
|
|
125
109
|
if TYPE_CHECKING:
|
|
126
110
|
from reflex.components.component import Component
|
|
@@ -289,10 +273,6 @@ class EventHandlerSetVar(EventHandler):
|
|
|
289
273
|
return super().__call__(*args)
|
|
290
274
|
|
|
291
275
|
|
|
292
|
-
if TYPE_CHECKING:
|
|
293
|
-
from pydantic.v1.fields import ModelField
|
|
294
|
-
|
|
295
|
-
|
|
296
276
|
def _unwrap_field_type(type_: Type) -> Type:
|
|
297
277
|
"""Unwrap rx.Field type annotations.
|
|
298
278
|
|
|
@@ -347,6 +327,9 @@ async def _resolve_delta(delta: Delta) -> Delta:
|
|
|
347
327
|
return delta
|
|
348
328
|
|
|
349
329
|
|
|
330
|
+
all_base_state_classes: dict[str, None] = {}
|
|
331
|
+
|
|
332
|
+
|
|
350
333
|
class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
351
334
|
"""The state of the app."""
|
|
352
335
|
|
|
@@ -644,6 +627,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
644
627
|
cls._var_dependencies = {}
|
|
645
628
|
cls._init_var_dependency_dicts()
|
|
646
629
|
|
|
630
|
+
all_base_state_classes[cls.get_full_name()] = None
|
|
631
|
+
|
|
647
632
|
@staticmethod
|
|
648
633
|
def _copy_fn(fn: Callable) -> Callable:
|
|
649
634
|
"""Copy a function. Used to copy ComputedVars and EventHandlers from mixins.
|
|
@@ -1742,6 +1727,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1742
1727
|
|
|
1743
1728
|
Yields:
|
|
1744
1729
|
StateUpdate object
|
|
1730
|
+
|
|
1731
|
+
Raises:
|
|
1732
|
+
ValueError: If a string value is received for an int or float type and cannot be converted.
|
|
1745
1733
|
"""
|
|
1746
1734
|
from reflex.utils import telemetry
|
|
1747
1735
|
|
|
@@ -1779,12 +1767,25 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1779
1767
|
hinted_args, (Base, BaseModelV1, BaseModelV2)
|
|
1780
1768
|
):
|
|
1781
1769
|
payload[arg] = hinted_args(**value)
|
|
1782
|
-
|
|
1770
|
+
elif isinstance(value, list) and (hinted_args is set or hinted_args is Set):
|
|
1783
1771
|
payload[arg] = set(value)
|
|
1784
|
-
|
|
1772
|
+
elif isinstance(value, list) and (
|
|
1785
1773
|
hinted_args is tuple or hinted_args is Tuple
|
|
1786
1774
|
):
|
|
1787
1775
|
payload[arg] = tuple(value)
|
|
1776
|
+
elif isinstance(value, str) and (
|
|
1777
|
+
hinted_args is int or hinted_args is float
|
|
1778
|
+
):
|
|
1779
|
+
try:
|
|
1780
|
+
payload[arg] = hinted_args(value)
|
|
1781
|
+
except ValueError:
|
|
1782
|
+
raise ValueError(
|
|
1783
|
+
f"Received a string value ({value}) for {arg} but expected a {hinted_args}"
|
|
1784
|
+
) from None
|
|
1785
|
+
else:
|
|
1786
|
+
console.warn(
|
|
1787
|
+
f"Received a string value ({value}) for {arg} but expected a {hinted_args}. A simple conversion was successful."
|
|
1788
|
+
)
|
|
1788
1789
|
|
|
1789
1790
|
# Wrap the function in a try/except block.
|
|
1790
1791
|
try:
|
|
@@ -2459,6 +2460,8 @@ class ComponentState(State, mixin=True):
|
|
|
2459
2460
|
Returns:
|
|
2460
2461
|
A new instance of the Component with an independent copy of the State.
|
|
2461
2462
|
"""
|
|
2463
|
+
from reflex.compiler.compiler import into_component
|
|
2464
|
+
|
|
2462
2465
|
cls._per_component_state_instance_count += 1
|
|
2463
2466
|
state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
|
|
2464
2467
|
component_state = type(
|
|
@@ -2470,6 +2473,7 @@ class ComponentState(State, mixin=True):
|
|
|
2470
2473
|
# Save a reference to the dynamic state for pickle/unpickle.
|
|
2471
2474
|
setattr(reflex.istate.dynamic, state_cls_name, component_state)
|
|
2472
2475
|
component = component_state.get_component(*children, **props)
|
|
2476
|
+
component = into_component(component)
|
|
2473
2477
|
component.State = component_state
|
|
2474
2478
|
return component
|
|
2475
2479
|
|
|
@@ -4088,6 +4092,7 @@ def reload_state_module(
|
|
|
4088
4092
|
for subclass in tuple(state.class_subclasses):
|
|
4089
4093
|
reload_state_module(module=module, state=subclass)
|
|
4090
4094
|
if subclass.__module__ == module and module is not None:
|
|
4095
|
+
all_base_state_classes.pop(subclass.get_full_name(), None)
|
|
4091
4096
|
state.class_subclasses.remove(subclass)
|
|
4092
4097
|
state._always_dirty_substates.discard(subclass.get_name())
|
|
4093
4098
|
state._var_dependencies = {}
|
reflex/style.py
CHANGED
|
@@ -12,7 +12,7 @@ from reflex.utils.exceptions import ReflexError
|
|
|
12
12
|
from reflex.utils.imports import ImportVar
|
|
13
13
|
from reflex.utils.types import get_origin
|
|
14
14
|
from reflex.vars import VarData
|
|
15
|
-
from reflex.vars.base import
|
|
15
|
+
from reflex.vars.base import LiteralVar, Var
|
|
16
16
|
from reflex.vars.function import FunctionVar
|
|
17
17
|
from reflex.vars.object import ObjectVar
|
|
18
18
|
|
|
@@ -48,7 +48,6 @@ def _color_mode_var(_js_expr: str, _var_type: Type = str) -> Var:
|
|
|
48
48
|
).guess_type()
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
@CallableVar
|
|
52
51
|
def set_color_mode(
|
|
53
52
|
new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None,
|
|
54
53
|
) -> Var[EventChain]:
|
|
@@ -191,11 +190,12 @@ def convert(
|
|
|
191
190
|
for key, value in style_dict.items():
|
|
192
191
|
keys = (
|
|
193
192
|
format_style_key(key)
|
|
194
|
-
if not isinstance(value, (dict, ObjectVar))
|
|
193
|
+
if not isinstance(value, (dict, ObjectVar, list))
|
|
195
194
|
or (
|
|
196
195
|
isinstance(value, Breakpoints)
|
|
197
196
|
and all(not isinstance(v, dict) for v in value.values())
|
|
198
197
|
)
|
|
198
|
+
or (isinstance(value, list) and all(not isinstance(v, dict) for v in value))
|
|
199
199
|
or (
|
|
200
200
|
isinstance(value, ObjectVar)
|
|
201
201
|
and not issubclass(get_origin(value._var_type) or value._var_type, dict)
|
|
@@ -237,7 +237,9 @@ def format_style_key(key: str) -> Tuple[str, ...]:
|
|
|
237
237
|
Returns:
|
|
238
238
|
Tuple of css style names corresponding to the key provided.
|
|
239
239
|
"""
|
|
240
|
-
key
|
|
240
|
+
if key.startswith("--"):
|
|
241
|
+
return (key,)
|
|
242
|
+
key = format.to_camel_case(key)
|
|
241
243
|
return STYLE_PROP_SHORTHAND_MAPPING.get(key, (key,))
|
|
242
244
|
|
|
243
245
|
|
reflex/testing.py
CHANGED
|
@@ -43,6 +43,7 @@ import reflex.utils.exec
|
|
|
43
43
|
import reflex.utils.format
|
|
44
44
|
import reflex.utils.prerequisites
|
|
45
45
|
import reflex.utils.processes
|
|
46
|
+
from reflex.components.component import CustomComponent
|
|
46
47
|
from reflex.config import environment
|
|
47
48
|
from reflex.state import (
|
|
48
49
|
BaseState,
|
|
@@ -254,6 +255,7 @@ class AppHarness:
|
|
|
254
255
|
# disable telemetry reporting for tests
|
|
255
256
|
|
|
256
257
|
os.environ["TELEMETRY_ENABLED"] = "false"
|
|
258
|
+
CustomComponent.create().get_component.cache_clear()
|
|
257
259
|
self.app_path.mkdir(parents=True, exist_ok=True)
|
|
258
260
|
if self.app_source is not None:
|
|
259
261
|
app_globals = self._get_globals_from_signature(self.app_source)
|
reflex/utils/build.py
CHANGED
|
@@ -60,6 +60,7 @@ def _zip(
|
|
|
60
60
|
dirs_to_exclude: set[str] | None = None,
|
|
61
61
|
files_to_exclude: set[str] | None = None,
|
|
62
62
|
top_level_dirs_to_exclude: set[str] | None = None,
|
|
63
|
+
globs_to_include: list[str] | None = None,
|
|
63
64
|
) -> None:
|
|
64
65
|
"""Zip utility function.
|
|
65
66
|
|
|
@@ -72,6 +73,7 @@ def _zip(
|
|
|
72
73
|
dirs_to_exclude: The directories to exclude.
|
|
73
74
|
files_to_exclude: The files to exclude.
|
|
74
75
|
top_level_dirs_to_exclude: The top level directory names immediately under root_dir to exclude. Do not exclude folders by these names further in the sub-directories.
|
|
76
|
+
globs_to_include: Apply these globs from the root_dir and always include them in the zip.
|
|
75
77
|
|
|
76
78
|
"""
|
|
77
79
|
target = Path(target)
|
|
@@ -103,6 +105,13 @@ def _zip(
|
|
|
103
105
|
files_to_zip += [
|
|
104
106
|
str(root / file) for file in files if file not in files_to_exclude
|
|
105
107
|
]
|
|
108
|
+
if globs_to_include:
|
|
109
|
+
for glob in globs_to_include:
|
|
110
|
+
files_to_zip += [
|
|
111
|
+
str(file)
|
|
112
|
+
for file in root_dir.glob(glob)
|
|
113
|
+
if file.name not in files_to_exclude
|
|
114
|
+
]
|
|
106
115
|
|
|
107
116
|
# Create a progress bar for zipping the component.
|
|
108
117
|
progress = Progress(
|
|
@@ -160,6 +169,9 @@ def zip_app(
|
|
|
160
169
|
top_level_dirs_to_exclude={"assets"},
|
|
161
170
|
exclude_venv_dirs=True,
|
|
162
171
|
upload_db_file=upload_db_file,
|
|
172
|
+
globs_to_include=[
|
|
173
|
+
str(Path(constants.Dirs.WEB) / constants.Dirs.BACKEND / "*")
|
|
174
|
+
],
|
|
163
175
|
)
|
|
164
176
|
|
|
165
177
|
|
reflex/utils/console.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import contextlib
|
|
6
6
|
import inspect
|
|
7
|
+
import os
|
|
7
8
|
import shutil
|
|
8
9
|
import time
|
|
9
10
|
from pathlib import Path
|
|
@@ -60,6 +61,9 @@ def set_log_level(log_level: LogLevel):
|
|
|
60
61
|
f"log_level must be a LogLevel enum value, got {log_level} of type {type(log_level)} instead."
|
|
61
62
|
)
|
|
62
63
|
global _LOG_LEVEL
|
|
64
|
+
if log_level != _LOG_LEVEL:
|
|
65
|
+
# Set the loglevel persistenly for subprocesses.
|
|
66
|
+
os.environ["LOGLEVEL"] = log_level.value
|
|
63
67
|
_LOG_LEVEL = log_level
|
|
64
68
|
|
|
65
69
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Decorator utilities."""
|
|
2
|
+
|
|
3
|
+
from typing import Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def once(f: Callable[[], T]) -> Callable[[], T]:
|
|
9
|
+
"""A decorator that calls the function once and caches the result.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
f: The function to call.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
A function that calls the function once and caches the result.
|
|
16
|
+
"""
|
|
17
|
+
unset = object()
|
|
18
|
+
value: object | T = unset
|
|
19
|
+
|
|
20
|
+
def wrapper() -> T:
|
|
21
|
+
nonlocal value
|
|
22
|
+
value = f() if value is unset else value
|
|
23
|
+
return value # pyright: ignore[reportReturnType]
|
|
24
|
+
|
|
25
|
+
return wrapper
|
reflex/utils/exec.py
CHANGED
|
@@ -10,6 +10,7 @@ import re
|
|
|
10
10
|
import subprocess
|
|
11
11
|
import sys
|
|
12
12
|
from pathlib import Path
|
|
13
|
+
from typing import Sequence
|
|
13
14
|
from urllib.parse import urljoin
|
|
14
15
|
|
|
15
16
|
import psutil
|
|
@@ -242,29 +243,63 @@ def run_backend(
|
|
|
242
243
|
run_uvicorn_backend(host, port, loglevel)
|
|
243
244
|
|
|
244
245
|
|
|
245
|
-
def
|
|
246
|
-
"""Get the reload
|
|
246
|
+
def get_reload_paths() -> Sequence[Path]:
|
|
247
|
+
"""Get the reload paths for the backend.
|
|
247
248
|
|
|
248
249
|
Returns:
|
|
249
|
-
The reload
|
|
250
|
+
The reload paths for the backend.
|
|
250
251
|
"""
|
|
251
252
|
config = get_config()
|
|
252
|
-
|
|
253
|
+
reload_paths = [Path(config.app_name).parent]
|
|
253
254
|
if config.app_module is not None and config.app_module.__file__:
|
|
254
255
|
module_path = Path(config.app_module.__file__).resolve().parent
|
|
255
256
|
|
|
256
|
-
while module_path.parent.name
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
module_path = module_path.parent
|
|
263
|
-
else:
|
|
264
|
-
break
|
|
257
|
+
while module_path.parent.name and any(
|
|
258
|
+
sibling_file.name == "__init__.py"
|
|
259
|
+
for sibling_file in module_path.parent.iterdir()
|
|
260
|
+
):
|
|
261
|
+
# go up a level to find dir without `__init__.py`
|
|
262
|
+
module_path = module_path.parent
|
|
265
263
|
|
|
266
|
-
|
|
267
|
-
|
|
264
|
+
reload_paths = [module_path]
|
|
265
|
+
|
|
266
|
+
include_dirs = tuple(
|
|
267
|
+
map(Path.absolute, environment.REFLEX_HOT_RELOAD_INCLUDE_PATHS.get())
|
|
268
|
+
)
|
|
269
|
+
exclude_dirs = tuple(
|
|
270
|
+
map(Path.absolute, environment.REFLEX_HOT_RELOAD_EXCLUDE_PATHS.get())
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def is_excluded_by_default(path: Path) -> bool:
|
|
274
|
+
if path.is_dir():
|
|
275
|
+
if path.name.startswith("."):
|
|
276
|
+
# exclude hidden directories
|
|
277
|
+
return True
|
|
278
|
+
if path.name.startswith("__"):
|
|
279
|
+
# ignore things like __pycache__
|
|
280
|
+
return True
|
|
281
|
+
return path.name in (".gitignore", "uploaded_files")
|
|
282
|
+
|
|
283
|
+
reload_paths = (
|
|
284
|
+
tuple(
|
|
285
|
+
path.absolute()
|
|
286
|
+
for dir in reload_paths
|
|
287
|
+
for path in dir.iterdir()
|
|
288
|
+
if not is_excluded_by_default(path)
|
|
289
|
+
)
|
|
290
|
+
+ include_dirs
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if exclude_dirs:
|
|
294
|
+
reload_paths = tuple(
|
|
295
|
+
path
|
|
296
|
+
for path in reload_paths
|
|
297
|
+
if all(not path.samefile(exclude) for exclude in exclude_dirs)
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
console.debug(f"Reload paths: {list(map(str, reload_paths))}")
|
|
301
|
+
|
|
302
|
+
return reload_paths
|
|
268
303
|
|
|
269
304
|
|
|
270
305
|
def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
|
|
@@ -283,7 +318,7 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
283
318
|
port=port,
|
|
284
319
|
log_level=loglevel.value,
|
|
285
320
|
reload=True,
|
|
286
|
-
reload_dirs=list(map(str,
|
|
321
|
+
reload_dirs=list(map(str, get_reload_paths())),
|
|
287
322
|
)
|
|
288
323
|
|
|
289
324
|
|
|
@@ -310,8 +345,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
310
345
|
interface=Interfaces.ASGI,
|
|
311
346
|
log_level=LogLevels(loglevel.value),
|
|
312
347
|
reload=True,
|
|
313
|
-
reload_paths=
|
|
314
|
-
reload_ignore_dirs=[".web", ".states"],
|
|
348
|
+
reload_paths=get_reload_paths(),
|
|
315
349
|
).serve()
|
|
316
350
|
except ImportError:
|
|
317
351
|
console.error(
|
|
@@ -368,34 +402,49 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
|
|
|
368
402
|
|
|
369
403
|
app_module = get_app_module()
|
|
370
404
|
|
|
371
|
-
run_backend_prod = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
|
|
372
|
-
run_backend_prod_windows = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
|
|
373
405
|
command = (
|
|
374
406
|
[
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
407
|
+
"uvicorn",
|
|
408
|
+
*(
|
|
409
|
+
[
|
|
410
|
+
"--limit-max-requests",
|
|
411
|
+
str(config.gunicorn_max_requests),
|
|
412
|
+
]
|
|
413
|
+
if config.gunicorn_max_requests > 0
|
|
414
|
+
else []
|
|
415
|
+
),
|
|
416
|
+
*("--timeout-keep-alive", str(config.timeout)),
|
|
417
|
+
*("--host", host),
|
|
418
|
+
*("--port", str(port)),
|
|
419
|
+
*("--workers", str(_get_backend_workers())),
|
|
380
420
|
app_module,
|
|
381
421
|
]
|
|
382
422
|
if constants.IS_WINDOWS
|
|
383
423
|
else [
|
|
384
|
-
|
|
385
|
-
"--
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
424
|
+
"gunicorn",
|
|
425
|
+
*("--worker-class", config.gunicorn_worker_class),
|
|
426
|
+
*(
|
|
427
|
+
[
|
|
428
|
+
"--max-requests",
|
|
429
|
+
str(config.gunicorn_max_requests),
|
|
430
|
+
"--max-requests-jitter",
|
|
431
|
+
str(config.gunicorn_max_requests_jitter),
|
|
432
|
+
]
|
|
433
|
+
if config.gunicorn_max_requests > 0
|
|
434
|
+
else []
|
|
435
|
+
),
|
|
436
|
+
"--preload",
|
|
437
|
+
*("--timeout", str(config.timeout)),
|
|
438
|
+
*("--bind", f"{host}:{port}"),
|
|
439
|
+
*("--threads", str(_get_backend_workers())),
|
|
389
440
|
f"{app_module}()",
|
|
390
441
|
]
|
|
391
442
|
)
|
|
392
443
|
|
|
393
444
|
command += [
|
|
394
|
-
"--log-level",
|
|
395
|
-
loglevel.value,
|
|
396
|
-
"--workers",
|
|
397
|
-
str(_get_backend_workers()),
|
|
445
|
+
*("--log-level", loglevel.value),
|
|
398
446
|
]
|
|
447
|
+
|
|
399
448
|
processes.new_process(
|
|
400
449
|
command,
|
|
401
450
|
run=True,
|
|
@@ -535,3 +584,12 @@ def is_prod_mode() -> bool:
|
|
|
535
584
|
"""
|
|
536
585
|
current_mode = environment.REFLEX_ENV_MODE.get()
|
|
537
586
|
return current_mode == constants.Env.PROD
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def get_compile_context() -> constants.CompileContext:
|
|
590
|
+
"""Check if the app is compiled for deploy.
|
|
591
|
+
|
|
592
|
+
Returns:
|
|
593
|
+
Whether the app is being compiled for deploy.
|
|
594
|
+
"""
|
|
595
|
+
return environment.REFLEX_COMPILE_CONTEXT.get()
|