reflex 0.8.3a1__py3-none-any.whl → 0.8.3a3__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/components/radix/themes/components/separator.py +4 -4
- reflex/components/radix/themes/components/separator.pyi +3 -3
- reflex/environment.py +0 -3
- reflex/event.py +2 -2
- reflex/plugins/base.py +2 -2
- reflex/route.py +4 -4
- reflex/testing.py +287 -164
- reflex/utils/console.py +3 -20
- reflex/utils/exec.py +4 -5
- reflex/utils/format.py +1 -1
- {reflex-0.8.3a1.dist-info → reflex-0.8.3a3.dist-info}/METADATA +1 -1
- {reflex-0.8.3a1.dist-info → reflex-0.8.3a3.dist-info}/RECORD +15 -15
- {reflex-0.8.3a1.dist-info → reflex-0.8.3a3.dist-info}/WHEEL +0 -0
- {reflex-0.8.3a1.dist-info → reflex-0.8.3a3.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.3a1.dist-info → reflex-0.8.3a3.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,7 +6,7 @@ from reflex.components.core.breakpoints import Responsive
|
|
|
6
6
|
from reflex.components.radix.themes.base import LiteralAccentColor, RadixThemesComponent
|
|
7
7
|
from reflex.vars.base import LiteralVar, Var
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
LiteralSeparatorSize = Literal["1", "2", "3", "4"]
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Separator(RadixThemesComponent):
|
|
@@ -14,10 +14,10 @@ class Separator(RadixThemesComponent):
|
|
|
14
14
|
|
|
15
15
|
tag = "Separator"
|
|
16
16
|
|
|
17
|
-
# The size of the
|
|
18
|
-
size: Var[Responsive[
|
|
17
|
+
# The size of the separator: "1" | "2" | "3" | "4"
|
|
18
|
+
size: Var[Responsive[LiteralSeparatorSize]] = LiteralVar.create("4")
|
|
19
19
|
|
|
20
|
-
# The color of the
|
|
20
|
+
# The color of the separator
|
|
21
21
|
color_scheme: Var[LiteralAccentColor]
|
|
22
22
|
|
|
23
23
|
# The orientation of the separator.
|
|
@@ -11,7 +11,7 @@ from reflex.components.radix.themes.base import RadixThemesComponent
|
|
|
11
11
|
from reflex.event import EventType, PointerEventInfo
|
|
12
12
|
from reflex.vars.base import Var
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
LiteralSeparatorSize = Literal["1", "2", "3", "4"]
|
|
15
15
|
|
|
16
16
|
class Separator(RadixThemesComponent):
|
|
17
17
|
@classmethod
|
|
@@ -127,8 +127,8 @@ class Separator(RadixThemesComponent):
|
|
|
127
127
|
|
|
128
128
|
Args:
|
|
129
129
|
*children: Child components.
|
|
130
|
-
size: The size of the
|
|
131
|
-
color_scheme: The color of the
|
|
130
|
+
size: The size of the separator: "1" | "2" | "3" | "4"
|
|
131
|
+
color_scheme: The color of the separator
|
|
132
132
|
orientation: The orientation of the separator.
|
|
133
133
|
decorative: When true, signifies that it is purely visual, carries no semantic meaning, and ensures it is not present in the accessibility tree.
|
|
134
134
|
style: The style of the component.
|
reflex/environment.py
CHANGED
|
@@ -650,9 +650,6 @@ class EnvironmentVariables:
|
|
|
650
650
|
# Enable full logging of debug messages to reflex user directory.
|
|
651
651
|
REFLEX_ENABLE_FULL_LOGGING: EnvVar[bool] = env_var(False)
|
|
652
652
|
|
|
653
|
-
# The path to the reflex errors log file. If not set, no separate error log will be used.
|
|
654
|
-
REFLEX_ERROR_LOG_FILE: EnvVar[Path | None] = env_var(None)
|
|
655
|
-
|
|
656
653
|
|
|
657
654
|
environment = EnvironmentVariables()
|
|
658
655
|
|
reflex/event.py
CHANGED
|
@@ -577,7 +577,7 @@ class JavascriptInputEvent:
|
|
|
577
577
|
init=True,
|
|
578
578
|
frozen=True,
|
|
579
579
|
)
|
|
580
|
-
class
|
|
580
|
+
class JavascriptKeyboardEvent:
|
|
581
581
|
"""Interface for a Javascript KeyboardEvent https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent."""
|
|
582
582
|
|
|
583
583
|
key: str = ""
|
|
@@ -645,7 +645,7 @@ class KeyInputInfo(TypedDict):
|
|
|
645
645
|
|
|
646
646
|
|
|
647
647
|
def key_event(
|
|
648
|
-
e: ObjectVar[
|
|
648
|
+
e: ObjectVar[JavascriptKeyboardEvent],
|
|
649
649
|
) -> tuple[Var[str], Var[KeyInputInfo]]:
|
|
650
650
|
"""Get the key from a keyboard event.
|
|
651
651
|
|
reflex/plugins/base.py
CHANGED
|
@@ -17,7 +17,7 @@ class CommonContext(TypedDict):
|
|
|
17
17
|
P = ParamSpec("P")
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class
|
|
20
|
+
class AddTaskProtocol(Protocol):
|
|
21
21
|
"""Protocol for adding a task to the pre-compile context."""
|
|
22
22
|
|
|
23
23
|
def __call__(
|
|
@@ -39,7 +39,7 @@ class AddTaskProtcol(Protocol):
|
|
|
39
39
|
class PreCompileContext(CommonContext):
|
|
40
40
|
"""Context for pre-compile hooks."""
|
|
41
41
|
|
|
42
|
-
add_save_task:
|
|
42
|
+
add_save_task: AddTaskProtocol
|
|
43
43
|
add_modify_task: Callable[[str, Callable[[str], str]], None]
|
|
44
44
|
unevaluated_pages: Sequence["UnevaluatedPage"]
|
|
45
45
|
|
reflex/route.py
CHANGED
|
@@ -131,7 +131,7 @@ def replace_brackets_with_keywords(input_string: str) -> str:
|
|
|
131
131
|
)
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
def
|
|
134
|
+
def route_specificity(keyworded_route: str) -> tuple[int, int, int]:
|
|
135
135
|
"""Get the specificity of a route with keywords.
|
|
136
136
|
|
|
137
137
|
The smaller the number, the more specific the route is.
|
|
@@ -193,13 +193,13 @@ def get_router(routes: list[str]) -> Callable[[str], str | None]:
|
|
|
193
193
|
keyworded_routes = {
|
|
194
194
|
replace_brackets_with_keywords(route): route for route in routes
|
|
195
195
|
}
|
|
196
|
-
|
|
196
|
+
sorted_routes_by_specificity = sorted(
|
|
197
197
|
keyworded_routes.items(),
|
|
198
|
-
key=lambda item:
|
|
198
|
+
key=lambda item: route_specificity(item[0]),
|
|
199
199
|
)
|
|
200
200
|
regexed_routes = [
|
|
201
201
|
(get_route_regex(keyworded_route), original_route)
|
|
202
|
-
for keyworded_route, original_route in
|
|
202
|
+
for keyworded_route, original_route in sorted_routes_by_specificity
|
|
203
203
|
]
|
|
204
204
|
|
|
205
205
|
def get_route(path: str) -> str | None:
|
reflex/testing.py
CHANGED
|
@@ -10,6 +10,7 @@ import inspect
|
|
|
10
10
|
import os
|
|
11
11
|
import platform
|
|
12
12
|
import re
|
|
13
|
+
import signal
|
|
13
14
|
import socket
|
|
14
15
|
import socketserver
|
|
15
16
|
import subprocess
|
|
@@ -18,16 +19,17 @@ import textwrap
|
|
|
18
19
|
import threading
|
|
19
20
|
import time
|
|
20
21
|
import types
|
|
21
|
-
from collections.abc import Callable, Coroutine, Sequence
|
|
22
|
+
from collections.abc import AsyncIterator, Callable, Coroutine, Sequence
|
|
22
23
|
from http.server import SimpleHTTPRequestHandler
|
|
23
|
-
from io import TextIOWrapper
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
|
26
26
|
|
|
27
|
-
import
|
|
27
|
+
import uvicorn
|
|
28
28
|
|
|
29
29
|
import reflex
|
|
30
|
+
import reflex.environment
|
|
30
31
|
import reflex.reflex
|
|
32
|
+
import reflex.utils.build
|
|
31
33
|
import reflex.utils.exec
|
|
32
34
|
import reflex.utils.format
|
|
33
35
|
import reflex.utils.prerequisites
|
|
@@ -43,7 +45,9 @@ from reflex.state import (
|
|
|
43
45
|
StateManagerRedis,
|
|
44
46
|
reload_state_module,
|
|
45
47
|
)
|
|
48
|
+
from reflex.utils import console
|
|
46
49
|
from reflex.utils.export import export
|
|
50
|
+
from reflex.utils.types import ASGIApp
|
|
47
51
|
|
|
48
52
|
try:
|
|
49
53
|
from selenium import webdriver
|
|
@@ -97,14 +101,6 @@ class chdir(contextlib.AbstractContextManager): # noqa: N801
|
|
|
97
101
|
os.chdir(self._old_cwd.pop())
|
|
98
102
|
|
|
99
103
|
|
|
100
|
-
class ReflexProcessLoggedErrorError(RuntimeError):
|
|
101
|
-
"""Exception raised when the reflex process logs contain errors."""
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
class ReflexProcessExitNonZeroError(RuntimeError):
|
|
105
|
-
"""Exception raised when the reflex process exits with a non-zero status."""
|
|
106
|
-
|
|
107
|
-
|
|
108
104
|
@dataclasses.dataclass
|
|
109
105
|
class AppHarness:
|
|
110
106
|
"""AppHarness executes a reflex app in-process for testing."""
|
|
@@ -117,15 +113,14 @@ class AppHarness:
|
|
|
117
113
|
app_module_path: Path
|
|
118
114
|
app_module: types.ModuleType | None = None
|
|
119
115
|
app_instance: reflex.App | None = None
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
reflex_process_error_log_path: Path | None = None
|
|
116
|
+
app_asgi: ASGIApp | None = None
|
|
117
|
+
frontend_process: subprocess.Popen | None = None
|
|
123
118
|
frontend_url: str | None = None
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
frontend_output_thread: threading.Thread | None = None
|
|
120
|
+
backend_thread: threading.Thread | None = None
|
|
121
|
+
backend: uvicorn.Server | None = None
|
|
126
122
|
state_manager: StateManager | None = None
|
|
127
123
|
_frontends: list[WebDriver] = dataclasses.field(default_factory=list)
|
|
128
|
-
_reflex_process_log_fn: TextIOWrapper | None = None
|
|
129
124
|
|
|
130
125
|
@classmethod
|
|
131
126
|
def create(
|
|
@@ -278,117 +273,82 @@ class AppHarness:
|
|
|
278
273
|
# Ensure the AppHarness test does not skip State assignment due to running via pytest
|
|
279
274
|
os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None)
|
|
280
275
|
os.environ[reflex.constants.APP_HARNESS_FLAG] = "true"
|
|
276
|
+
# Ensure we actually compile the app during first initialization.
|
|
281
277
|
self.app_instance, self.app_module = (
|
|
282
278
|
reflex.utils.prerequisites.get_and_validate_app(
|
|
283
279
|
# Do not reload the module for pre-existing apps (only apps generated from source)
|
|
284
280
|
reload=self.app_source is not None
|
|
285
281
|
)
|
|
286
282
|
)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
283
|
+
self.app_asgi = self.app_instance()
|
|
284
|
+
if self.app_instance and isinstance(
|
|
285
|
+
self.app_instance._state_manager, StateManagerRedis
|
|
286
|
+
):
|
|
287
|
+
if self.app_instance._state is None:
|
|
288
|
+
msg = "State is not set."
|
|
289
|
+
raise RuntimeError(msg)
|
|
290
|
+
# Create our own redis connection for testing.
|
|
291
|
+
self.state_manager = StateManagerRedis.create(self.app_instance._state)
|
|
292
|
+
else:
|
|
293
|
+
self.state_manager = (
|
|
294
|
+
self.app_instance._state_manager if self.app_instance else None
|
|
295
295
|
)
|
|
296
296
|
|
|
297
297
|
def _reload_state_module(self):
|
|
298
298
|
"""Reload the rx.State module to avoid conflict when reloading."""
|
|
299
299
|
reload_state_module(module=f"{self.app_name}.{self.app_name}")
|
|
300
300
|
|
|
301
|
-
def
|
|
302
|
-
self
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
def _get_backend_shutdown_handler(self):
|
|
302
|
+
if self.backend is None:
|
|
303
|
+
msg = "Backend was not initialized."
|
|
304
|
+
raise RuntimeError(msg)
|
|
305
305
|
|
|
306
|
-
|
|
307
|
-
backend: Whether to start the backend server.
|
|
308
|
-
frontend: Whether to start the frontend server.
|
|
309
|
-
mode: The mode to run the app in (dev, prod, etc.).
|
|
310
|
-
"""
|
|
311
|
-
self.reflex_process_log_path = self.app_path / "reflex.log"
|
|
312
|
-
self.reflex_process_error_log_path = self.app_path / "reflex_error.log"
|
|
313
|
-
self._reflex_process_log_fn = self.reflex_process_log_path.open("w")
|
|
314
|
-
command = [
|
|
315
|
-
sys.executable,
|
|
316
|
-
"-u",
|
|
317
|
-
"-m",
|
|
318
|
-
"reflex",
|
|
319
|
-
"run",
|
|
320
|
-
"--env",
|
|
321
|
-
mode,
|
|
322
|
-
"--loglevel",
|
|
323
|
-
"debug",
|
|
324
|
-
]
|
|
325
|
-
if backend:
|
|
326
|
-
if self.backend_port is None:
|
|
327
|
-
self.backend_port = reflex.utils.processes.handle_port(
|
|
328
|
-
"backend", 48000, auto_increment=True
|
|
329
|
-
)
|
|
330
|
-
command.extend(["--backend-port", str(self.backend_port)])
|
|
331
|
-
if not frontend:
|
|
332
|
-
command.append("--backend-only")
|
|
333
|
-
if frontend:
|
|
334
|
-
if self.frontend_port is None:
|
|
335
|
-
self.frontend_port = reflex.utils.processes.handle_port(
|
|
336
|
-
"frontend", 43000, auto_increment=True
|
|
337
|
-
)
|
|
338
|
-
command.extend(["--frontend-port", str(self.frontend_port)])
|
|
339
|
-
if not backend:
|
|
340
|
-
command.append("--frontend-only")
|
|
341
|
-
self.reflex_process = subprocess.Popen(
|
|
342
|
-
command,
|
|
343
|
-
stdout=self._reflex_process_log_fn,
|
|
344
|
-
stderr=self._reflex_process_log_fn,
|
|
345
|
-
cwd=self.app_path,
|
|
346
|
-
env={
|
|
347
|
-
**os.environ,
|
|
348
|
-
"REFLEX_ERROR_LOG_FILE": str(self.reflex_process_error_log_path),
|
|
349
|
-
"PYTEST_CURRENT_TEST": "",
|
|
350
|
-
"APP_HARNESS_FLAG": "true",
|
|
351
|
-
},
|
|
352
|
-
)
|
|
353
|
-
self._wait_for_servers(backend=backend, frontend=frontend)
|
|
306
|
+
original_shutdown = self.backend.shutdown
|
|
354
307
|
|
|
355
|
-
|
|
356
|
-
|
|
308
|
+
async def _shutdown(*args, **kwargs) -> None:
|
|
309
|
+
# ensure redis is closed before event loop
|
|
310
|
+
if self.app_instance is not None and isinstance(
|
|
311
|
+
self.app_instance._state_manager, StateManagerRedis
|
|
312
|
+
):
|
|
313
|
+
with contextlib.suppress(ValueError):
|
|
314
|
+
await self.app_instance._state_manager.close()
|
|
357
315
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
316
|
+
# socketio shutdown handler
|
|
317
|
+
if self.app_instance is not None and self.app_instance.sio is not None:
|
|
318
|
+
with contextlib.suppress(TypeError):
|
|
319
|
+
await self.app_instance.sio.shutdown()
|
|
361
320
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
321
|
+
# sqlalchemy async engine shutdown handler
|
|
322
|
+
try:
|
|
323
|
+
async_engine = reflex.model.get_async_engine(None)
|
|
324
|
+
except ValueError:
|
|
325
|
+
pass
|
|
326
|
+
else:
|
|
327
|
+
await async_engine.dispose()
|
|
368
328
|
|
|
369
|
-
|
|
370
|
-
backend_ready = False
|
|
371
|
-
timeout = 30
|
|
372
|
-
start_time = time.time()
|
|
329
|
+
await original_shutdown(*args, **kwargs)
|
|
373
330
|
|
|
374
|
-
|
|
375
|
-
while not ((not frontend or frontend_ready) and (not backend or backend_ready)):
|
|
376
|
-
if time.time() - start_time > timeout:
|
|
377
|
-
msg = f"Timeout waiting for servers. Frontend ready: {frontend_ready}, Backend ready: {backend_ready}"
|
|
378
|
-
raise RuntimeError(msg)
|
|
331
|
+
return _shutdown
|
|
379
332
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
333
|
+
def _start_backend(self, port: int = 0):
|
|
334
|
+
if self.app_asgi is None:
|
|
335
|
+
msg = "App was not initialized."
|
|
336
|
+
raise RuntimeError(msg)
|
|
337
|
+
self.backend = uvicorn.Server(
|
|
338
|
+
uvicorn.Config(
|
|
339
|
+
app=self.app_asgi,
|
|
340
|
+
host="127.0.0.1",
|
|
341
|
+
port=port,
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
self.backend.shutdown = self._get_backend_shutdown_handler()
|
|
345
|
+
with chdir(self.app_path):
|
|
346
|
+
print( # noqa: T201
|
|
347
|
+
"Creating backend in a new thread..."
|
|
348
|
+
) # for pytest diagnosis
|
|
349
|
+
self.backend_thread = threading.Thread(target=self.backend.run)
|
|
350
|
+
self.backend_thread.start()
|
|
351
|
+
print("Backend started.") # for pytest diagnosis #noqa: T201
|
|
392
352
|
|
|
393
353
|
async def _reset_backend_state_manager(self):
|
|
394
354
|
"""Reset the StateManagerRedis event loop affinity.
|
|
@@ -416,14 +376,78 @@ class AppHarness:
|
|
|
416
376
|
msg = "Failed to reset state manager."
|
|
417
377
|
raise RuntimeError(msg)
|
|
418
378
|
|
|
379
|
+
def _start_frontend(self):
|
|
380
|
+
# Set up the frontend.
|
|
381
|
+
with chdir(self.app_path):
|
|
382
|
+
config = reflex.config.get_config()
|
|
383
|
+
print("Polling for servers...") # for pytest diagnosis #noqa: T201
|
|
384
|
+
config.api_url = "http://{}:{}".format(
|
|
385
|
+
*self._poll_for_servers(timeout=30).getsockname(),
|
|
386
|
+
)
|
|
387
|
+
print("Building frontend...") # for pytest diagnosis #noqa: T201
|
|
388
|
+
reflex.utils.build.setup_frontend(self.app_path)
|
|
389
|
+
|
|
390
|
+
print("Frontend starting...") # for pytest diagnosis #noqa: T201
|
|
391
|
+
|
|
392
|
+
# Start the frontend.
|
|
393
|
+
self.frontend_process = reflex.utils.processes.new_process(
|
|
394
|
+
[
|
|
395
|
+
*reflex.utils.prerequisites.get_js_package_executor(raise_on_none=True)[
|
|
396
|
+
0
|
|
397
|
+
],
|
|
398
|
+
"run",
|
|
399
|
+
"dev",
|
|
400
|
+
],
|
|
401
|
+
cwd=self.app_path / reflex.utils.prerequisites.get_web_dir(),
|
|
402
|
+
env={"PORT": "0", "NO_COLOR": "1"},
|
|
403
|
+
**FRONTEND_POPEN_ARGS,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
def _wait_frontend(self):
|
|
407
|
+
if self.frontend_process is None or self.frontend_process.stdout is None:
|
|
408
|
+
msg = "Frontend process has no stdout."
|
|
409
|
+
raise RuntimeError(msg)
|
|
410
|
+
while self.frontend_url is None:
|
|
411
|
+
line = self.frontend_process.stdout.readline()
|
|
412
|
+
if not line:
|
|
413
|
+
break
|
|
414
|
+
print(line) # for pytest diagnosis #noqa: T201
|
|
415
|
+
m = re.search(reflex.constants.ReactRouter.FRONTEND_LISTENING_REGEX, line)
|
|
416
|
+
if m is not None:
|
|
417
|
+
self.frontend_url = m.group(1)
|
|
418
|
+
config = reflex.config.get_config()
|
|
419
|
+
config.deploy_url = self.frontend_url
|
|
420
|
+
break
|
|
421
|
+
if self.frontend_url is None:
|
|
422
|
+
msg = "Frontend did not start"
|
|
423
|
+
raise RuntimeError(msg)
|
|
424
|
+
|
|
425
|
+
def consume_frontend_output():
|
|
426
|
+
while True:
|
|
427
|
+
try:
|
|
428
|
+
line = (
|
|
429
|
+
self.frontend_process.stdout.readline() # pyright: ignore [reportOptionalMemberAccess]
|
|
430
|
+
)
|
|
431
|
+
# catch I/O operation on closed file.
|
|
432
|
+
except ValueError as e:
|
|
433
|
+
console.error(str(e))
|
|
434
|
+
break
|
|
435
|
+
if not line:
|
|
436
|
+
break
|
|
437
|
+
|
|
438
|
+
self.frontend_output_thread = threading.Thread(target=consume_frontend_output)
|
|
439
|
+
self.frontend_output_thread.start()
|
|
440
|
+
|
|
419
441
|
def start(self) -> AppHarness:
|
|
420
|
-
"""Start the
|
|
442
|
+
"""Start the backend in a new thread and dev frontend as a separate process.
|
|
421
443
|
|
|
422
444
|
Returns:
|
|
423
445
|
self
|
|
424
446
|
"""
|
|
425
447
|
self._initialize_app()
|
|
426
|
-
self.
|
|
448
|
+
self._start_backend()
|
|
449
|
+
self._start_frontend()
|
|
450
|
+
self._wait_frontend()
|
|
427
451
|
return self
|
|
428
452
|
|
|
429
453
|
@staticmethod
|
|
@@ -452,48 +476,43 @@ class AppHarness:
|
|
|
452
476
|
return self.start()
|
|
453
477
|
|
|
454
478
|
def stop(self) -> None:
|
|
455
|
-
"""Stop the
|
|
479
|
+
"""Stop the frontend and backend servers."""
|
|
480
|
+
import psutil
|
|
481
|
+
|
|
482
|
+
# Quit browsers first to avoid any lingering events being sent during shutdown.
|
|
456
483
|
for driver in self._frontends:
|
|
457
484
|
driver.quit()
|
|
458
485
|
|
|
459
|
-
self._stop_reflex()
|
|
460
486
|
self._reload_state_module()
|
|
461
487
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
self.
|
|
467
|
-
|
|
468
|
-
):
|
|
469
|
-
try:
|
|
470
|
-
# Kill server and children recursively.
|
|
471
|
-
reflex.utils.exec.kill(self.reflex_process.pid)
|
|
472
|
-
except (ProcessLookupError, OSError):
|
|
473
|
-
pass
|
|
474
|
-
finally:
|
|
475
|
-
self.reflex_process = None
|
|
476
|
-
if self._reflex_process_log_fn is not None:
|
|
477
|
-
with contextlib.suppress(Exception):
|
|
478
|
-
self._reflex_process_log_fn.close()
|
|
479
|
-
if self.reflex_process_log_path is not None:
|
|
480
|
-
print(self.reflex_process_log_path.read_text()) # noqa: T201 for pytest debugging
|
|
481
|
-
# If there are errors in the logs, raise an exception.
|
|
482
|
-
if (
|
|
483
|
-
self.reflex_process_error_log_path is not None
|
|
484
|
-
and self.reflex_process_error_log_path.exists()
|
|
485
|
-
):
|
|
486
|
-
error_log_content = self.reflex_process_error_log_path.read_text()
|
|
487
|
-
if error_log_content:
|
|
488
|
-
msg = f"Reflex process error log contains errors:\n{error_log_content}"
|
|
489
|
-
raise ReflexProcessLoggedErrorError(msg)
|
|
490
|
-
# When the process exits non-zero, but wasn't killed, it is a test failure.
|
|
491
|
-
if returncode is not None and returncode != 0:
|
|
492
|
-
msg = (
|
|
493
|
-
f"Reflex process exited with code {returncode}. "
|
|
494
|
-
"Check the logs for more details."
|
|
488
|
+
if self.backend is not None:
|
|
489
|
+
self.backend.should_exit = True
|
|
490
|
+
if self.frontend_process is not None:
|
|
491
|
+
# https://stackoverflow.com/a/70565806
|
|
492
|
+
frontend_children = psutil.Process(self.frontend_process.pid).children(
|
|
493
|
+
recursive=True,
|
|
495
494
|
)
|
|
496
|
-
|
|
495
|
+
if sys.platform == "win32":
|
|
496
|
+
self.frontend_process.terminate()
|
|
497
|
+
else:
|
|
498
|
+
pgrp = os.getpgid(self.frontend_process.pid)
|
|
499
|
+
os.killpg(pgrp, signal.SIGTERM)
|
|
500
|
+
# kill any remaining child processes
|
|
501
|
+
for child in frontend_children:
|
|
502
|
+
# It's okay if the process is already gone.
|
|
503
|
+
with contextlib.suppress(psutil.NoSuchProcess):
|
|
504
|
+
child.terminate()
|
|
505
|
+
_, still_alive = psutil.wait_procs(frontend_children, timeout=3)
|
|
506
|
+
for child in still_alive:
|
|
507
|
+
# It's okay if the process is already gone.
|
|
508
|
+
with contextlib.suppress(psutil.NoSuchProcess):
|
|
509
|
+
child.kill()
|
|
510
|
+
# wait for main process to exit
|
|
511
|
+
self.frontend_process.communicate()
|
|
512
|
+
if self.backend_thread is not None:
|
|
513
|
+
self.backend_thread.join()
|
|
514
|
+
if self.frontend_output_thread is not None:
|
|
515
|
+
self.frontend_output_thread.join()
|
|
497
516
|
|
|
498
517
|
def __exit__(self, *excinfo) -> None:
|
|
499
518
|
"""Contextmanager protocol for `stop()`.
|
|
@@ -562,6 +581,39 @@ class AppHarness:
|
|
|
562
581
|
await asyncio.sleep(step)
|
|
563
582
|
return False
|
|
564
583
|
|
|
584
|
+
def _poll_for_servers(self, timeout: TimeoutType = None) -> socket.socket:
|
|
585
|
+
"""Poll backend server for listening sockets.
|
|
586
|
+
|
|
587
|
+
Args:
|
|
588
|
+
timeout: how long to wait for listening socket.
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
first active listening socket on the backend
|
|
592
|
+
|
|
593
|
+
Raises:
|
|
594
|
+
RuntimeError: when the backend hasn't started running
|
|
595
|
+
TimeoutError: when server or sockets are not ready
|
|
596
|
+
"""
|
|
597
|
+
if self.backend is None:
|
|
598
|
+
msg = "Backend is not running."
|
|
599
|
+
raise RuntimeError(msg)
|
|
600
|
+
backend = self.backend
|
|
601
|
+
# check for servers to be initialized
|
|
602
|
+
if not self._poll_for(
|
|
603
|
+
target=lambda: getattr(backend, "servers", False),
|
|
604
|
+
timeout=timeout,
|
|
605
|
+
):
|
|
606
|
+
msg = "Backend servers are not initialized."
|
|
607
|
+
raise TimeoutError(msg)
|
|
608
|
+
# check for sockets to be listening
|
|
609
|
+
if not self._poll_for(
|
|
610
|
+
target=lambda: getattr(backend.servers[0], "sockets", False),
|
|
611
|
+
timeout=timeout,
|
|
612
|
+
):
|
|
613
|
+
msg = "Backend is not listening."
|
|
614
|
+
raise TimeoutError(msg)
|
|
615
|
+
return backend.servers[0].sockets[0]
|
|
616
|
+
|
|
565
617
|
def frontend(
|
|
566
618
|
self,
|
|
567
619
|
driver_clz: type[WebDriver] | None = None,
|
|
@@ -648,18 +700,71 @@ class AppHarness:
|
|
|
648
700
|
The state instance associated with the given token
|
|
649
701
|
|
|
650
702
|
Raises:
|
|
651
|
-
|
|
703
|
+
RuntimeError: when the app hasn't started running
|
|
652
704
|
"""
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
705
|
+
if self.state_manager is None:
|
|
706
|
+
msg = "state_manager is not set."
|
|
707
|
+
raise RuntimeError(msg)
|
|
656
708
|
try:
|
|
657
709
|
return await self.state_manager.get_state(token)
|
|
658
710
|
finally:
|
|
659
|
-
await self._reset_backend_state_manager()
|
|
660
711
|
if isinstance(self.state_manager, StateManagerRedis):
|
|
661
712
|
await self.state_manager.close()
|
|
662
713
|
|
|
714
|
+
async def set_state(self, token: str, **kwargs) -> None:
|
|
715
|
+
"""Set the state associated with the given token.
|
|
716
|
+
|
|
717
|
+
Args:
|
|
718
|
+
token: The state token to set.
|
|
719
|
+
kwargs: Attributes to set on the state.
|
|
720
|
+
|
|
721
|
+
Raises:
|
|
722
|
+
RuntimeError: when the app hasn't started running
|
|
723
|
+
"""
|
|
724
|
+
if self.state_manager is None:
|
|
725
|
+
msg = "state_manager is not set."
|
|
726
|
+
raise RuntimeError(msg)
|
|
727
|
+
state = await self.get_state(token)
|
|
728
|
+
for key, value in kwargs.items():
|
|
729
|
+
setattr(state, key, value)
|
|
730
|
+
try:
|
|
731
|
+
await self.state_manager.set_state(token, state)
|
|
732
|
+
finally:
|
|
733
|
+
if isinstance(self.state_manager, StateManagerRedis):
|
|
734
|
+
await self.state_manager.close()
|
|
735
|
+
|
|
736
|
+
@contextlib.asynccontextmanager
|
|
737
|
+
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
|
738
|
+
"""Modify the state associated with the given token and send update to frontend.
|
|
739
|
+
|
|
740
|
+
Args:
|
|
741
|
+
token: The state token to modify
|
|
742
|
+
|
|
743
|
+
Yields:
|
|
744
|
+
The state instance associated with the given token
|
|
745
|
+
|
|
746
|
+
Raises:
|
|
747
|
+
RuntimeError: when the app hasn't started running
|
|
748
|
+
"""
|
|
749
|
+
if self.state_manager is None:
|
|
750
|
+
msg = "state_manager is not set."
|
|
751
|
+
raise RuntimeError(msg)
|
|
752
|
+
if self.app_instance is None:
|
|
753
|
+
msg = "App is not running."
|
|
754
|
+
raise RuntimeError(msg)
|
|
755
|
+
app_state_manager = self.app_instance.state_manager
|
|
756
|
+
if isinstance(self.state_manager, StateManagerRedis):
|
|
757
|
+
# Temporarily replace the app's state manager with our own, since
|
|
758
|
+
# the redis connection is on the backend_thread event loop
|
|
759
|
+
self.app_instance._state_manager = self.state_manager
|
|
760
|
+
try:
|
|
761
|
+
async with self.app_instance.modify_state(token) as state:
|
|
762
|
+
yield state
|
|
763
|
+
finally:
|
|
764
|
+
if isinstance(self.state_manager, StateManagerRedis):
|
|
765
|
+
self.app_instance._state_manager = app_state_manager
|
|
766
|
+
await self.state_manager.close()
|
|
767
|
+
|
|
663
768
|
def poll_for_content(
|
|
664
769
|
self,
|
|
665
770
|
element: WebElement,
|
|
@@ -902,7 +1007,7 @@ class AppHarnessProd(AppHarness):
|
|
|
902
1007
|
root=web_root,
|
|
903
1008
|
error_page_map=error_page_map,
|
|
904
1009
|
) as self.frontend_server:
|
|
905
|
-
self.frontend_url = "http://localhost:{1}
|
|
1010
|
+
self.frontend_url = "http://localhost:{1}".format(
|
|
906
1011
|
*self.frontend_server.socket.getsockname()
|
|
907
1012
|
)
|
|
908
1013
|
self.frontend_server.serve_forever()
|
|
@@ -911,7 +1016,10 @@ class AppHarnessProd(AppHarness):
|
|
|
911
1016
|
# Set up the frontend.
|
|
912
1017
|
with chdir(self.app_path):
|
|
913
1018
|
config = reflex.config.get_config()
|
|
914
|
-
|
|
1019
|
+
print("Polling for servers...") # for pytest diagnosis #noqa: T201
|
|
1020
|
+
config.api_url = "http://{}:{}".format(
|
|
1021
|
+
*self._poll_for_servers(timeout=30).getsockname(),
|
|
1022
|
+
)
|
|
915
1023
|
print("Building frontend...") # for pytest diagnosis #noqa: T201
|
|
916
1024
|
|
|
917
1025
|
get_config().loglevel = reflex.constants.LogLevel.INFO
|
|
@@ -940,17 +1048,32 @@ class AppHarnessProd(AppHarness):
|
|
|
940
1048
|
msg = "Frontend did not start"
|
|
941
1049
|
raise RuntimeError(msg)
|
|
942
1050
|
|
|
943
|
-
def
|
|
944
|
-
|
|
1051
|
+
def _start_backend(self):
|
|
1052
|
+
if self.app_asgi is None:
|
|
1053
|
+
msg = "App was not initialized."
|
|
1054
|
+
raise RuntimeError(msg)
|
|
1055
|
+
environment.REFLEX_SKIP_COMPILE.set(True)
|
|
1056
|
+
self.backend = uvicorn.Server(
|
|
1057
|
+
uvicorn.Config(
|
|
1058
|
+
app=self.app_asgi,
|
|
1059
|
+
host="127.0.0.1",
|
|
1060
|
+
port=0,
|
|
1061
|
+
workers=reflex.utils.processes.get_num_workers(),
|
|
1062
|
+
),
|
|
1063
|
+
)
|
|
1064
|
+
self.backend.shutdown = self._get_backend_shutdown_handler()
|
|
1065
|
+
print( # noqa: T201
|
|
1066
|
+
"Creating backend in a new thread..."
|
|
1067
|
+
)
|
|
1068
|
+
self.backend_thread = threading.Thread(target=self.backend.run)
|
|
1069
|
+
self.backend_thread.start()
|
|
1070
|
+
print("Backend started.") # for pytest diagnosis #noqa: T201
|
|
945
1071
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
self._start_frontend()
|
|
952
|
-
self._wait_frontend()
|
|
953
|
-
return self
|
|
1072
|
+
def _poll_for_servers(self, timeout: TimeoutType = None) -> socket.socket:
|
|
1073
|
+
try:
|
|
1074
|
+
return super()._poll_for_servers(timeout)
|
|
1075
|
+
finally:
|
|
1076
|
+
environment.REFLEX_SKIP_COMPILE.set(None)
|
|
954
1077
|
|
|
955
1078
|
def stop(self):
|
|
956
1079
|
"""Stop the frontend python webserver."""
|
reflex/utils/console.py
CHANGED
|
@@ -31,7 +31,7 @@ _EMITTED_DEPRECATION_WARNINGS = set()
|
|
|
31
31
|
_EMITTED_INFO = set()
|
|
32
32
|
|
|
33
33
|
# Warnings which have been printed.
|
|
34
|
-
|
|
34
|
+
_EMITTED_WARNINGS = set()
|
|
35
35
|
|
|
36
36
|
# Errors which have been printed.
|
|
37
37
|
_EMITTED_ERRORS = set()
|
|
@@ -128,21 +128,6 @@ def should_use_log_file_console() -> bool:
|
|
|
128
128
|
return environment.REFLEX_ENABLE_FULL_LOGGING.get()
|
|
129
129
|
|
|
130
130
|
|
|
131
|
-
@once
|
|
132
|
-
def error_log_file_console():
|
|
133
|
-
"""Create a console that logs errors to a file.
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
A Console object that logs errors to a file.
|
|
137
|
-
"""
|
|
138
|
-
from reflex.environment import environment
|
|
139
|
-
|
|
140
|
-
if not (env_error_log_file := environment.REFLEX_ERROR_LOG_FILE.get()):
|
|
141
|
-
return None
|
|
142
|
-
env_error_log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
143
|
-
return Console(file=env_error_log_file.open("a", encoding="utf-8"))
|
|
144
|
-
|
|
145
|
-
|
|
146
131
|
def print_to_log_file(msg: str, *, dedupe: bool = False, **kwargs):
|
|
147
132
|
"""Print a message to the log file.
|
|
148
133
|
|
|
@@ -250,9 +235,9 @@ def warn(msg: str, *, dedupe: bool = False, **kwargs):
|
|
|
250
235
|
"""
|
|
251
236
|
if _LOG_LEVEL <= LogLevel.WARNING:
|
|
252
237
|
if dedupe:
|
|
253
|
-
if msg in
|
|
238
|
+
if msg in _EMITTED_WARNINGS:
|
|
254
239
|
return
|
|
255
|
-
|
|
240
|
+
_EMITTED_WARNINGS.add(msg)
|
|
256
241
|
print(f"[orange1]Warning: {msg}[/orange1]", **kwargs)
|
|
257
242
|
if should_use_log_file_console():
|
|
258
243
|
print_to_log_file(f"[orange1]Warning: {msg}[/orange1]", **kwargs)
|
|
@@ -343,8 +328,6 @@ def error(msg: str, *, dedupe: bool = False, **kwargs):
|
|
|
343
328
|
print(f"[red]{msg}[/red]", **kwargs)
|
|
344
329
|
if should_use_log_file_console():
|
|
345
330
|
print_to_log_file(f"[red]{msg}[/red]", **kwargs)
|
|
346
|
-
if error_log_file := error_log_file_console():
|
|
347
|
-
error_log_file.print(f"[red]{msg}[/red]", **kwargs)
|
|
348
331
|
|
|
349
332
|
|
|
350
333
|
def ask(
|
reflex/utils/exec.py
CHANGED
|
@@ -405,7 +405,10 @@ def get_reload_paths() -> Sequence[Path]:
|
|
|
405
405
|
module_path = module_path.parent
|
|
406
406
|
|
|
407
407
|
while module_path.parent.name and _has_child_file(module_path, "__init__.py"):
|
|
408
|
-
if
|
|
408
|
+
if (
|
|
409
|
+
_has_child_file(module_path, "rxconfig.py")
|
|
410
|
+
and module_path == Path.cwd()
|
|
411
|
+
):
|
|
409
412
|
init_file = module_path / "__init__.py"
|
|
410
413
|
init_file_content = init_file.read_text()
|
|
411
414
|
if init_file_content.strip():
|
|
@@ -603,8 +606,6 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
|
|
|
603
606
|
|
|
604
607
|
# Our default args, then env args (env args win on conflicts)
|
|
605
608
|
command = [
|
|
606
|
-
sys.executable,
|
|
607
|
-
"-m",
|
|
608
609
|
"gunicorn",
|
|
609
610
|
"--preload",
|
|
610
611
|
*("--worker-class", "uvicorn.workers.UvicornH11Worker"),
|
|
@@ -641,8 +642,6 @@ def run_granian_backend_prod(host: str, port: int, loglevel: LogLevel):
|
|
|
641
642
|
from reflex.utils import processes
|
|
642
643
|
|
|
643
644
|
command = [
|
|
644
|
-
sys.executable,
|
|
645
|
-
"-m",
|
|
646
645
|
"granian",
|
|
647
646
|
*("--log-level", "critical"),
|
|
648
647
|
*("--host", host),
|
reflex/utils/format.py
CHANGED
|
@@ -251,7 +251,7 @@ def _escape_js_string(string: str) -> str:
|
|
|
251
251
|
The escaped string.
|
|
252
252
|
"""
|
|
253
253
|
|
|
254
|
-
# TODO: we may need to re-
|
|
254
|
+
# TODO: we may need to re-visit this logic after new Var API is implemented.
|
|
255
255
|
def escape_outside_segments(segment: str):
|
|
256
256
|
"""Escape backticks in segments outside of `${}`.
|
|
257
257
|
|
|
@@ -6,16 +6,16 @@ reflex/app.py,sha256=xrP6jq5D5tILtNzNluhRJ_kkWuRIekj8NFA5plA-Spc,75596
|
|
|
6
6
|
reflex/assets.py,sha256=l5O_mlrTprC0lF7Rc_McOe3a0OtSLnRdNl_PqCpDCBA,3431
|
|
7
7
|
reflex/base.py,sha256=Oh664QL3fZEHErhUasFqP7fE4olYf1y-9Oj6uZI2FCU,1173
|
|
8
8
|
reflex/config.py,sha256=HgJ57Op-glTro23GoQKmyXwUplvGYgZFKjvClYpD27s,19359
|
|
9
|
-
reflex/environment.py,sha256=
|
|
10
|
-
reflex/event.py,sha256=
|
|
9
|
+
reflex/environment.py,sha256=PQF1QSLgu_tpUUP0vXWdvuC4t3wFts94nw2urt9Id8o,23227
|
|
10
|
+
reflex/event.py,sha256=sLjwMSsfQ3sZowvg-Og3gTVU4mqdwD587Q9Psgdfyzw,72776
|
|
11
11
|
reflex/model.py,sha256=l1-6fm7NHRFWH-xK9oV9UzAVfvKeUXG1f-tCrF7vmfI,19403
|
|
12
12
|
reflex/page.py,sha256=Bn8FTlUtjjKwUtpQA5r5eaWE_ZUb8i4XgrQi8FWbzNA,1880
|
|
13
13
|
reflex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
reflex/reflex.py,sha256=1sRT6INn5ZdbPZAL-g5C9hU6ZwMEMHJV6gUvNTx3KSg,22616
|
|
15
|
-
reflex/route.py,sha256=
|
|
15
|
+
reflex/route.py,sha256=ZRlXl1HeKWM3W-yFCnRFIA_6I3W9JBqe2CKzCpWX7Vg,7699
|
|
16
16
|
reflex/state.py,sha256=mrYV9wEdJ9DdWbAadgdWHbxc6onOU56dT8f4zkbznPo,92127
|
|
17
17
|
reflex/style.py,sha256=JxbXXA4MTnXrk0XHEoMBoNC7J-M2oL5Hl3W_QmXvmBg,13222
|
|
18
|
-
reflex/testing.py,sha256=
|
|
18
|
+
reflex/testing.py,sha256=6EXQN9K0tYfzEDe2aSRh4xLM_Jb_oIrI_qH2F_e0KXc,39423
|
|
19
19
|
reflex/.templates/apps/blank/assets/favicon.ico,sha256=baxxgDAQ2V4-G5Q4S2yK5uUJTUGkv-AOWBQ0xd6myUo,4286
|
|
20
20
|
reflex/.templates/apps/blank/code/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
reflex/.templates/apps/blank/code/blank.py,sha256=wry9E3VjC7qtt_gzqNOyo4KZAAlzVyNp3uhFkcLZmM0,898
|
|
@@ -251,8 +251,8 @@ reflex/components/radix/themes/components/segmented_control.py,sha256=E2kqQ8rQ45
|
|
|
251
251
|
reflex/components/radix/themes/components/segmented_control.pyi,sha256=ehTNqJjQLI7tO2uZD6s037ntbYAYe9t3w5M8jjTe6eA,8109
|
|
252
252
|
reflex/components/radix/themes/components/select.py,sha256=pHOKI3H954ZlVHK5lf074D6SuEsEglaZVeJkgPFDGPE,8083
|
|
253
253
|
reflex/components/radix/themes/components/select.pyi,sha256=-CdGS9Nog-kNRE6Q81GadBiciI_2MxlHeJtkwF74WxA,35724
|
|
254
|
-
reflex/components/radix/themes/components/separator.py,sha256=
|
|
255
|
-
reflex/components/radix/themes/components/separator.pyi,sha256=
|
|
254
|
+
reflex/components/radix/themes/components/separator.py,sha256=Yj0Y34gGTdm3LWcjtqGazWflkfpVBVEBHvXSAujbf5A,991
|
|
255
|
+
reflex/components/radix/themes/components/separator.pyi,sha256=YEfu92fbRCaGri7zhunvahjY4bmOlsZjB6UwvKUBiKI,5012
|
|
256
256
|
reflex/components/radix/themes/components/skeleton.py,sha256=oHltF5lOzE8T0poYtIXj3f2x8O_iZ56HCtx0a9AJ_Kw,918
|
|
257
257
|
reflex/components/radix/themes/components/skeleton.pyi,sha256=EuDA_WY91nrzZZ50M0UWdGSc37YCmJX7SHSneQXhPVk,3944
|
|
258
258
|
reflex/components/radix/themes/components/slider.py,sha256=8t-V8XTftdSZ3M7KBwu244k8JyGp6eMCz9t3vO5yLlQ,3440
|
|
@@ -364,7 +364,7 @@ reflex/middleware/__init__.py,sha256=x7xTeDuc73Hjj43k1J63naC9x8vzFxl4sq7cCFBX7sk
|
|
|
364
364
|
reflex/middleware/hydrate_middleware.py,sha256=1ch7bx2ZhojOR15b-LHD2JztrWCnpPJjTe8MWHJe-5Y,1510
|
|
365
365
|
reflex/middleware/middleware.py,sha256=p5VVoIgQ_NwOg_GOY6g0S4fmrV76_VE1zt-HiwbMw-s,1158
|
|
366
366
|
reflex/plugins/__init__.py,sha256=aARE63iAH2pNVdGmFU4qkXDiPoeBxINfkFzwk-NZe0w,351
|
|
367
|
-
reflex/plugins/base.py,sha256=
|
|
367
|
+
reflex/plugins/base.py,sha256=Jgj7Xrpk0ztM57kl0hN3PUHYrGSXu7oj7OPyIWQJUcE,3059
|
|
368
368
|
reflex/plugins/shared_tailwind.py,sha256=UXUndEEcYBZ02klymw-vSZv01IZVLJG3oSaBHpQ617U,6426
|
|
369
369
|
reflex/plugins/sitemap.py,sha256=Jj47uSMnkxndl7awkl48EhlQylBfY00WuMBNyTBcZHA,6186
|
|
370
370
|
reflex/plugins/tailwind_v3.py,sha256=7bXI-zsGoS1pW27-_gskxGaUOQ7NQMPcYkoI5lnmIMA,4819
|
|
@@ -373,12 +373,12 @@ reflex/utils/__init__.py,sha256=y-AHKiRQAhk2oAkvn7W8cRVTZVK625ff8tTwvZtO7S4,24
|
|
|
373
373
|
reflex/utils/build.py,sha256=lk8hE69onG95dv-LxRhjtEugct1g-KcWPUDorzqeGIE,7964
|
|
374
374
|
reflex/utils/codespaces.py,sha256=kEQ-j-jclTukFpXDlYgNp95kYMGDrQmP3VNEoYGZ1u4,3052
|
|
375
375
|
reflex/utils/compat.py,sha256=aSJH_M6iomgHPQ4onQ153xh1MWqPi3HSYDzE68N6gZM,2635
|
|
376
|
-
reflex/utils/console.py,sha256=
|
|
376
|
+
reflex/utils/console.py,sha256=_qC1KxqmIThrQnoCqIr4AMGgXfnByeF97YbA77PPuTQ,11531
|
|
377
377
|
reflex/utils/decorator.py,sha256=DVrlVGljV5OchMs-5_y1CbbqnCWlH6lv-dFko8yHxVY,1738
|
|
378
378
|
reflex/utils/exceptions.py,sha256=Wwu7Ji2xgq521bJKtU2NgjwhmFfnG8erirEVN2h8S-g,8884
|
|
379
|
-
reflex/utils/exec.py,sha256=
|
|
379
|
+
reflex/utils/exec.py,sha256=KQ5tS5vz3wuu9zDGPR6clSE0IoY_vcIbJGCB5bqiDJM,21987
|
|
380
380
|
reflex/utils/export.py,sha256=Z2AHuhkxGQzOi9I90BejQ4qEcD0URr2i-ZU5qTJt7eQ,2562
|
|
381
|
-
reflex/utils/format.py,sha256=
|
|
381
|
+
reflex/utils/format.py,sha256=FZe5NA0U3K0n0k7r8RIGcx-eHpN7sf8eQX9w1C8_uR8,21120
|
|
382
382
|
reflex/utils/imports.py,sha256=Ov-lqv-PfsPl3kTEW13r5aDauIfn6TqzEMyv42RKLOA,3761
|
|
383
383
|
reflex/utils/lazy_loader.py,sha256=BiY9OvmAJDCz10qpuyTYv9duXgMFQa6RXKQmTO9hqKU,4453
|
|
384
384
|
reflex/utils/misc.py,sha256=zbYIl7mI08is9enr851sj7PnDaNeVVvq5jDmQ4wdlCE,2879
|
|
@@ -401,8 +401,8 @@ reflex/vars/number.py,sha256=tO7pnvFaBsedq1HWT4skytnSqHWMluGEhUbjAUMx8XQ,28190
|
|
|
401
401
|
reflex/vars/object.py,sha256=BDmeiwG8v97s_BnR1Egq3NxOKVjv9TfnREB3cz0zZtk,17322
|
|
402
402
|
reflex/vars/sequence.py,sha256=1kBrqihspyjyQ1XDqFPC8OpVGtZs_EVkOdIKBro5ilA,55249
|
|
403
403
|
scripts/hatch_build.py,sha256=-4pxcLSFmirmujGpQX9UUxjhIC03tQ_fIQwVbHu9kc0,1861
|
|
404
|
-
reflex-0.8.
|
|
405
|
-
reflex-0.8.
|
|
406
|
-
reflex-0.8.
|
|
407
|
-
reflex-0.8.
|
|
408
|
-
reflex-0.8.
|
|
404
|
+
reflex-0.8.3a3.dist-info/METADATA,sha256=z4r4mAqtN5umJxNbaXQGmMl__HnItjgvmO_E2N3MiIM,12371
|
|
405
|
+
reflex-0.8.3a3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
406
|
+
reflex-0.8.3a3.dist-info/entry_points.txt,sha256=Rxt4dXc7MLBNt5CSHTehVPuSe9Xqow4HLX55nD9tQQ0,45
|
|
407
|
+
reflex-0.8.3a3.dist-info/licenses/LICENSE,sha256=dw3zLrp9f5ObD7kqS32vWfhcImfO52PMmRqvtxq_YEE,11358
|
|
408
|
+
reflex-0.8.3a3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|