reflex 0.8.3a2__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 +288 -180
- reflex/utils/console.py +3 -20
- reflex/utils/exec.py +4 -5
- reflex/utils/format.py +1 -1
- {reflex-0.8.3a2.dist-info → reflex-0.8.3a3.dist-info}/METADATA +1 -1
- {reflex-0.8.3a2.dist-info → reflex-0.8.3a3.dist-info}/RECORD +15 -15
- {reflex-0.8.3a2.dist-info → reflex-0.8.3a3.dist-info}/WHEEL +0 -0
- {reflex-0.8.3a2.dist-info → reflex-0.8.3a3.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.3a2.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,14 +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 uvicorn
|
|
28
|
+
|
|
27
29
|
import reflex
|
|
30
|
+
import reflex.environment
|
|
28
31
|
import reflex.reflex
|
|
32
|
+
import reflex.utils.build
|
|
29
33
|
import reflex.utils.exec
|
|
30
34
|
import reflex.utils.format
|
|
31
35
|
import reflex.utils.prerequisites
|
|
@@ -41,7 +45,9 @@ from reflex.state import (
|
|
|
41
45
|
StateManagerRedis,
|
|
42
46
|
reload_state_module,
|
|
43
47
|
)
|
|
48
|
+
from reflex.utils import console
|
|
44
49
|
from reflex.utils.export import export
|
|
50
|
+
from reflex.utils.types import ASGIApp
|
|
45
51
|
|
|
46
52
|
try:
|
|
47
53
|
from selenium import webdriver
|
|
@@ -95,32 +101,6 @@ class chdir(contextlib.AbstractContextManager): # noqa: N801
|
|
|
95
101
|
os.chdir(self._old_cwd.pop())
|
|
96
102
|
|
|
97
103
|
|
|
98
|
-
class ReflexProcessLoggedErrorError(RuntimeError):
|
|
99
|
-
"""Exception raised when the reflex process logs contain errors."""
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class ReflexProcessExitNonZeroError(RuntimeError):
|
|
103
|
-
"""Exception raised when the reflex process exits with a non-zero status."""
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def _is_port_responsive(port: int) -> bool:
|
|
107
|
-
"""Check if a port is responsive.
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
port: the port to check
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
True if the port is responsive, False otherwise
|
|
114
|
-
"""
|
|
115
|
-
try:
|
|
116
|
-
with contextlib.closing(
|
|
117
|
-
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
118
|
-
) as sock:
|
|
119
|
-
return sock.connect_ex(("127.0.0.1", port)) == 0
|
|
120
|
-
except (OverflowError, PermissionError, OSError):
|
|
121
|
-
return False
|
|
122
|
-
|
|
123
|
-
|
|
124
104
|
@dataclasses.dataclass
|
|
125
105
|
class AppHarness:
|
|
126
106
|
"""AppHarness executes a reflex app in-process for testing."""
|
|
@@ -133,15 +113,14 @@ class AppHarness:
|
|
|
133
113
|
app_module_path: Path
|
|
134
114
|
app_module: types.ModuleType | None = None
|
|
135
115
|
app_instance: reflex.App | None = None
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
reflex_process_error_log_path: Path | None = None
|
|
116
|
+
app_asgi: ASGIApp | None = None
|
|
117
|
+
frontend_process: subprocess.Popen | None = None
|
|
139
118
|
frontend_url: str | None = None
|
|
140
|
-
|
|
141
|
-
|
|
119
|
+
frontend_output_thread: threading.Thread | None = None
|
|
120
|
+
backend_thread: threading.Thread | None = None
|
|
121
|
+
backend: uvicorn.Server | None = None
|
|
142
122
|
state_manager: StateManager | None = None
|
|
143
123
|
_frontends: list[WebDriver] = dataclasses.field(default_factory=list)
|
|
144
|
-
_reflex_process_log_fn: TextIOWrapper | None = None
|
|
145
124
|
|
|
146
125
|
@classmethod
|
|
147
126
|
def create(
|
|
@@ -294,116 +273,82 @@ class AppHarness:
|
|
|
294
273
|
# Ensure the AppHarness test does not skip State assignment due to running via pytest
|
|
295
274
|
os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None)
|
|
296
275
|
os.environ[reflex.constants.APP_HARNESS_FLAG] = "true"
|
|
276
|
+
# Ensure we actually compile the app during first initialization.
|
|
297
277
|
self.app_instance, self.app_module = (
|
|
298
278
|
reflex.utils.prerequisites.get_and_validate_app(
|
|
299
279
|
# Do not reload the module for pre-existing apps (only apps generated from source)
|
|
300
280
|
reload=self.app_source is not None
|
|
301
281
|
)
|
|
302
282
|
)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
311
295
|
)
|
|
312
296
|
|
|
313
297
|
def _reload_state_module(self):
|
|
314
298
|
"""Reload the rx.State module to avoid conflict when reloading."""
|
|
315
299
|
reload_state_module(module=f"{self.app_name}.{self.app_name}")
|
|
316
300
|
|
|
317
|
-
def
|
|
318
|
-
self
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
Args:
|
|
323
|
-
backend: Whether to start the backend server.
|
|
324
|
-
frontend: Whether to start the frontend server.
|
|
325
|
-
mode: The mode to run the app in (dev, prod, etc.).
|
|
326
|
-
"""
|
|
327
|
-
self.reflex_process_log_path = self.app_path / "reflex.log"
|
|
328
|
-
self.reflex_process_error_log_path = self.app_path / "reflex_error.log"
|
|
329
|
-
self._reflex_process_log_fn = self.reflex_process_log_path.open("w")
|
|
330
|
-
command = [
|
|
331
|
-
sys.executable,
|
|
332
|
-
"-u",
|
|
333
|
-
"-m",
|
|
334
|
-
"reflex",
|
|
335
|
-
"run",
|
|
336
|
-
"--env",
|
|
337
|
-
mode,
|
|
338
|
-
"--loglevel",
|
|
339
|
-
"debug",
|
|
340
|
-
]
|
|
341
|
-
if backend:
|
|
342
|
-
if self.backend_port is None:
|
|
343
|
-
self.backend_port = reflex.utils.processes.handle_port(
|
|
344
|
-
"backend", 48000, auto_increment=True
|
|
345
|
-
)
|
|
346
|
-
command.extend(["--backend-port", str(self.backend_port)])
|
|
347
|
-
if not frontend:
|
|
348
|
-
command.append("--backend-only")
|
|
349
|
-
if frontend:
|
|
350
|
-
if self.frontend_port is None:
|
|
351
|
-
self.frontend_port = reflex.utils.processes.handle_port(
|
|
352
|
-
"frontend", 43000, auto_increment=True
|
|
353
|
-
)
|
|
354
|
-
command.extend(["--frontend-port", str(self.frontend_port)])
|
|
355
|
-
if not backend:
|
|
356
|
-
command.append("--frontend-only")
|
|
357
|
-
self.reflex_process = subprocess.Popen(
|
|
358
|
-
command,
|
|
359
|
-
stdout=self._reflex_process_log_fn,
|
|
360
|
-
stderr=self._reflex_process_log_fn,
|
|
361
|
-
cwd=self.app_path,
|
|
362
|
-
env={
|
|
363
|
-
**os.environ,
|
|
364
|
-
"REFLEX_ERROR_LOG_FILE": str(self.reflex_process_error_log_path),
|
|
365
|
-
"PYTEST_CURRENT_TEST": "",
|
|
366
|
-
"APP_HARNESS_FLAG": "true",
|
|
367
|
-
},
|
|
368
|
-
)
|
|
369
|
-
self._wait_for_servers(backend=backend, frontend=frontend)
|
|
301
|
+
def _get_backend_shutdown_handler(self):
|
|
302
|
+
if self.backend is None:
|
|
303
|
+
msg = "Backend was not initialized."
|
|
304
|
+
raise RuntimeError(msg)
|
|
370
305
|
|
|
371
|
-
|
|
372
|
-
"""Wait for both frontend and backend servers to be ready by parsing console output.
|
|
306
|
+
original_shutdown = self.backend.shutdown
|
|
373
307
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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()
|
|
377
315
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
msg = "Reflex process has no pid."
|
|
383
|
-
raise RuntimeError(msg)
|
|
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()
|
|
384
320
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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()
|
|
388
328
|
|
|
389
|
-
|
|
390
|
-
msg = "Frontend port is not set."
|
|
391
|
-
raise RuntimeError(msg)
|
|
329
|
+
await original_shutdown(*args, **kwargs)
|
|
392
330
|
|
|
393
|
-
|
|
394
|
-
backend_ready = False
|
|
331
|
+
return _shutdown
|
|
395
332
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
|
407
352
|
|
|
408
353
|
async def _reset_backend_state_manager(self):
|
|
409
354
|
"""Reset the StateManagerRedis event loop affinity.
|
|
@@ -431,14 +376,78 @@ class AppHarness:
|
|
|
431
376
|
msg = "Failed to reset state manager."
|
|
432
377
|
raise RuntimeError(msg)
|
|
433
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
|
+
|
|
434
441
|
def start(self) -> AppHarness:
|
|
435
|
-
"""Start the
|
|
442
|
+
"""Start the backend in a new thread and dev frontend as a separate process.
|
|
436
443
|
|
|
437
444
|
Returns:
|
|
438
445
|
self
|
|
439
446
|
"""
|
|
440
447
|
self._initialize_app()
|
|
441
|
-
self.
|
|
448
|
+
self._start_backend()
|
|
449
|
+
self._start_frontend()
|
|
450
|
+
self._wait_frontend()
|
|
442
451
|
return self
|
|
443
452
|
|
|
444
453
|
@staticmethod
|
|
@@ -467,48 +476,43 @@ class AppHarness:
|
|
|
467
476
|
return self.start()
|
|
468
477
|
|
|
469
478
|
def stop(self) -> None:
|
|
470
|
-
"""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.
|
|
471
483
|
for driver in self._frontends:
|
|
472
484
|
driver.quit()
|
|
473
485
|
|
|
474
|
-
self._stop_reflex()
|
|
475
486
|
self._reload_state_module()
|
|
476
487
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
self.
|
|
482
|
-
|
|
483
|
-
):
|
|
484
|
-
try:
|
|
485
|
-
# Kill server and children recursively.
|
|
486
|
-
reflex.utils.exec.kill(self.reflex_process.pid)
|
|
487
|
-
except (ProcessLookupError, OSError):
|
|
488
|
-
pass
|
|
489
|
-
finally:
|
|
490
|
-
self.reflex_process = None
|
|
491
|
-
if self._reflex_process_log_fn is not None:
|
|
492
|
-
with contextlib.suppress(Exception):
|
|
493
|
-
self._reflex_process_log_fn.close()
|
|
494
|
-
if self.reflex_process_log_path is not None:
|
|
495
|
-
print(self.reflex_process_log_path.read_text()) # noqa: T201 for pytest debugging
|
|
496
|
-
# If there are errors in the logs, raise an exception.
|
|
497
|
-
if (
|
|
498
|
-
self.reflex_process_error_log_path is not None
|
|
499
|
-
and self.reflex_process_error_log_path.exists()
|
|
500
|
-
):
|
|
501
|
-
error_log_content = self.reflex_process_error_log_path.read_text()
|
|
502
|
-
if error_log_content:
|
|
503
|
-
msg = f"Reflex process error log contains errors:\n{error_log_content}"
|
|
504
|
-
raise ReflexProcessLoggedErrorError(msg)
|
|
505
|
-
# When the process exits non-zero, but wasn't killed, it is a test failure.
|
|
506
|
-
if returncode is not None and returncode != 0:
|
|
507
|
-
msg = (
|
|
508
|
-
f"Reflex process exited with code {returncode}. "
|
|
509
|
-
"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,
|
|
510
494
|
)
|
|
511
|
-
|
|
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()
|
|
512
516
|
|
|
513
517
|
def __exit__(self, *excinfo) -> None:
|
|
514
518
|
"""Contextmanager protocol for `stop()`.
|
|
@@ -577,6 +581,39 @@ class AppHarness:
|
|
|
577
581
|
await asyncio.sleep(step)
|
|
578
582
|
return False
|
|
579
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
|
+
|
|
580
617
|
def frontend(
|
|
581
618
|
self,
|
|
582
619
|
driver_clz: type[WebDriver] | None = None,
|
|
@@ -663,18 +700,71 @@ class AppHarness:
|
|
|
663
700
|
The state instance associated with the given token
|
|
664
701
|
|
|
665
702
|
Raises:
|
|
666
|
-
|
|
703
|
+
RuntimeError: when the app hasn't started running
|
|
667
704
|
"""
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
705
|
+
if self.state_manager is None:
|
|
706
|
+
msg = "state_manager is not set."
|
|
707
|
+
raise RuntimeError(msg)
|
|
671
708
|
try:
|
|
672
709
|
return await self.state_manager.get_state(token)
|
|
673
710
|
finally:
|
|
674
|
-
await self._reset_backend_state_manager()
|
|
675
711
|
if isinstance(self.state_manager, StateManagerRedis):
|
|
676
712
|
await self.state_manager.close()
|
|
677
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
|
+
|
|
678
768
|
def poll_for_content(
|
|
679
769
|
self,
|
|
680
770
|
element: WebElement,
|
|
@@ -917,7 +1007,7 @@ class AppHarnessProd(AppHarness):
|
|
|
917
1007
|
root=web_root,
|
|
918
1008
|
error_page_map=error_page_map,
|
|
919
1009
|
) as self.frontend_server:
|
|
920
|
-
self.frontend_url = "http://localhost:{1}
|
|
1010
|
+
self.frontend_url = "http://localhost:{1}".format(
|
|
921
1011
|
*self.frontend_server.socket.getsockname()
|
|
922
1012
|
)
|
|
923
1013
|
self.frontend_server.serve_forever()
|
|
@@ -926,7 +1016,10 @@ class AppHarnessProd(AppHarness):
|
|
|
926
1016
|
# Set up the frontend.
|
|
927
1017
|
with chdir(self.app_path):
|
|
928
1018
|
config = reflex.config.get_config()
|
|
929
|
-
|
|
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
|
+
)
|
|
930
1023
|
print("Building frontend...") # for pytest diagnosis #noqa: T201
|
|
931
1024
|
|
|
932
1025
|
get_config().loglevel = reflex.constants.LogLevel.INFO
|
|
@@ -955,17 +1048,32 @@ class AppHarnessProd(AppHarness):
|
|
|
955
1048
|
msg = "Frontend did not start"
|
|
956
1049
|
raise RuntimeError(msg)
|
|
957
1050
|
|
|
958
|
-
def
|
|
959
|
-
|
|
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
|
|
960
1071
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
self._start_frontend()
|
|
967
|
-
self._wait_frontend()
|
|
968
|
-
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)
|
|
969
1077
|
|
|
970
1078
|
def stop(self):
|
|
971
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
|