reflex 0.2.2a1__py3-none-any.whl → 0.2.3__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/constants.py CHANGED
@@ -1,6 +1,8 @@
1
1
  """Constants used throughout the package."""
2
+ from __future__ import annotations
2
3
 
3
4
  import os
5
+ import platform
4
6
  import re
5
7
  from enum import Enum
6
8
  from types import SimpleNamespace
@@ -43,14 +45,10 @@ def get_value(key: str, default: Any = None, type_: Type = str) -> Type:
43
45
  MODULE_NAME = "reflex"
44
46
  # The current version of Reflex.
45
47
  VERSION = metadata.version(MODULE_NAME)
46
- # Minimum version of Node.js required to run Reflex.
47
- MIN_NODE_VERSION = "16.8.0"
48
-
49
- # Valid bun versions.
50
- MIN_BUN_VERSION = "0.5.9"
51
- MAX_BUN_VERSION = "0.6.9"
52
48
 
53
49
  # Files and directories used to init a new project.
50
+ # The directory to store reflex dependencies.
51
+ REFLEX_DIR = os.path.expandvars(os.path.join("$HOME", f".{MODULE_NAME}"))
54
52
  # The root directory of the reflex library.
55
53
  ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
56
54
  # The name of the assets directory.
@@ -64,6 +62,42 @@ ASSETS_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, APP_ASSETS_DIR)
64
62
  # The jinja template directory.
65
63
  JINJA_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "jinja")
66
64
 
65
+ # Bun config.
66
+ # The Bun version.
67
+ BUN_VERSION = "0.7.0"
68
+ # The directory to store the bun.
69
+ BUN_ROOT_PATH = os.path.join(REFLEX_DIR, ".bun")
70
+ # The bun path.
71
+ BUN_PATH = os.path.join(BUN_ROOT_PATH, "bin", "bun")
72
+ # URL to bun install script.
73
+ BUN_INSTALL_URL = "https://bun.sh/install"
74
+
75
+ # NVM / Node config.
76
+ # The NVM version.
77
+ NVM_VERSION = "0.39.1"
78
+ # The Node version.
79
+ NODE_VERSION = "18.17.0"
80
+ # The minimum required node version.
81
+ NODE_VERSION_MIN = "16.8.0"
82
+ # The directory to store nvm.
83
+ NVM_DIR = os.path.join(REFLEX_DIR, ".nvm")
84
+ # The nvm path.
85
+ NVM_PATH = os.path.join(NVM_DIR, "nvm.sh")
86
+ # The node bin path.
87
+ NODE_BIN_PATH = os.path.join(NVM_DIR, "versions", "node", f"v{NODE_VERSION}", "bin")
88
+ # The default path where node is installed.
89
+ NODE_PATH = (
90
+ "node" if platform.system() == "Windows" else os.path.join(NODE_BIN_PATH, "node")
91
+ )
92
+ # The default path where npm is installed.
93
+ NPM_PATH = (
94
+ "npm" if platform.system() == "Windows" else os.path.join(NODE_BIN_PATH, "npm")
95
+ )
96
+ # The URL to the nvm install script.
97
+ NVM_INSTALL_URL = (
98
+ f"https://raw.githubusercontent.com/nvm-sh/nvm/v{NVM_VERSION}/install.sh"
99
+ )
100
+
67
101
  # The frontend directories in a project.
68
102
  # The web folder where the NextJS app is compiled to.
69
103
  WEB_DIR = ".web"
@@ -110,12 +144,6 @@ BACKEND_PORT = get_value("BACKEND_PORT", "8000")
110
144
  API_URL = get_value("API_URL", "http://localhost:8000")
111
145
  # The deploy url
112
146
  DEPLOY_URL = get_value("DEPLOY_URL")
113
- # bun root location
114
- BUN_ROOT_PATH = "$HOME/.bun"
115
- # The default path where bun is installed.
116
- BUN_PATH = get_value("BUN_PATH", f"{BUN_ROOT_PATH}/bin/bun")
117
- # Command to install bun.
118
- INSTALL_BUN = f"curl -fsSL https://bun.sh/install | bash -s -- bun-v{MAX_BUN_VERSION}"
119
147
  # Default host in dev mode.
120
148
  BACKEND_HOST = get_value("BACKEND_HOST", "0.0.0.0")
121
149
  # The default timeout when launching the gunicorn server.
@@ -200,6 +228,11 @@ TOKEN_EXPIRATION = 60 * 60
200
228
  # The event namespace for websocket
201
229
  EVENT_NAMESPACE = get_value("EVENT_NAMESPACE")
202
230
 
231
+ # Testing variables.
232
+ # Testing os env set by pytest when running a test case.
233
+ PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
234
+
235
+
203
236
  # Env modes
204
237
  class Env(str, Enum):
205
238
  """The environment modes."""
@@ -218,6 +251,18 @@ class LogLevel(str, Enum):
218
251
  ERROR = "error"
219
252
  CRITICAL = "critical"
220
253
 
254
+ def __le__(self, other: LogLevel) -> bool:
255
+ """Compare log levels.
256
+
257
+ Args:
258
+ other: The other log level.
259
+
260
+ Returns:
261
+ True if the log level is less than or equal to the other log level.
262
+ """
263
+ levels = list(LogLevel)
264
+ return levels.index(self) <= levels.index(other)
265
+
221
266
 
222
267
  # Templates
223
268
  class Template(str, Enum):
@@ -328,6 +373,7 @@ class RouteVar(SimpleNamespace):
328
373
  CLIENT_TOKEN = "token"
329
374
  HEADERS = "headers"
330
375
  PATH = "pathname"
376
+ ORIGIN = "asPath"
331
377
  SESSION_ID = "sid"
332
378
  QUERY = "query"
333
379
  COOKIE = "cookie"
@@ -346,8 +392,7 @@ class RouteRegex(SimpleNamespace):
346
392
 
347
393
 
348
394
  # 404 variables
349
- ROOT_404 = ""
350
- SLUG_404 = "[..._]"
395
+ SLUG_404 = "404"
351
396
  TITLE_404 = "404 - Not Found"
352
397
  FAVICON_404 = "favicon.ico"
353
398
  DESCRIPTION_404 = "The page was not found"
reflex/model.py CHANGED
@@ -37,8 +37,8 @@ def get_engine(url: Optional[str] = None):
37
37
  if url is None:
38
38
  raise ValueError("No database url configured")
39
39
  if not Path(constants.ALEMBIC_CONFIG).exists():
40
- console.print(
41
- "[red]Database is not initialized, run [bold]reflex db init[/bold] first."
40
+ console.warn(
41
+ "Database is not initialized, run [bold]reflex db init[/bold] first."
42
42
  )
43
43
  echo_db_query = False
44
44
  if conf.env == constants.Env.DEV and constants.SQLALCHEMY_ECHO:
reflex/page.py ADDED
@@ -0,0 +1,66 @@
1
+ """The page decorator and associated variables and functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List, Optional, Union
6
+
7
+ from reflex.components.component import Component
8
+ from reflex.event import EventHandler
9
+
10
+ DECORATED_PAGES = []
11
+
12
+
13
+ def page(
14
+ route: Optional[str] = None,
15
+ title: Optional[str] = None,
16
+ image: Optional[str] = None,
17
+ description: Optional[str] = None,
18
+ meta: Optional[str] = None,
19
+ script_tags: Optional[List[Component]] = None,
20
+ on_load: Optional[Union[EventHandler, List[EventHandler]]] = None,
21
+ ):
22
+ """Decorate a function as a page.
23
+
24
+ rx.App() will automatically call add_page() for any method decorated with page
25
+ when App.compile is called.
26
+
27
+ All defaults are None because they will use the one from add_page().
28
+
29
+ Note: the decorated functions still need to be imported.
30
+
31
+ Args:
32
+ route: The route to reach the page.
33
+ title: The title of the page.
34
+ image: The favicon of the page.
35
+ description: The description of the page.
36
+ meta: Additionnal meta to add to the page.
37
+ on_load: The event handler(s) called when the page load.
38
+ script_tags: scripts to attach to the page
39
+
40
+ Returns:
41
+ The decorated function.
42
+ """
43
+ ...
44
+
45
+ def decorator(render_fn):
46
+ kwargs = {}
47
+ if route:
48
+ kwargs["route"] = route
49
+ if title:
50
+ kwargs["title"] = title
51
+ if image:
52
+ kwargs["image"] = image
53
+ if description:
54
+ kwargs["description"] = description
55
+ if meta:
56
+ kwargs["meta"] = meta
57
+ if script_tags:
58
+ kwargs["script_tags"] = script_tags
59
+ if on_load:
60
+ kwargs["on_load"] = on_load
61
+
62
+ DECORATED_PAGES.append((render_fn, kwargs))
63
+
64
+ return render_fn
65
+
66
+ return decorator
reflex/reflex.py CHANGED
@@ -1,7 +1,6 @@
1
1
  """Reflex CLI to create, run, and deploy apps."""
2
2
 
3
3
  import os
4
- import platform
5
4
  import signal
6
5
  import threading
7
6
  from pathlib import Path
@@ -15,54 +14,79 @@ from reflex.config import get_config
15
14
  from reflex.utils import build, console, exec, prerequisites, processes, telemetry
16
15
 
17
16
  # Create the app.
18
- cli = typer.Typer()
17
+ cli = typer.Typer(add_completion=False)
19
18
 
20
19
 
21
- @cli.command()
22
- def version():
23
- """Get the Reflex version."""
24
- console.print(constants.VERSION)
20
+ def version(value: bool):
21
+ """Get the Reflex version.
22
+
23
+ Args:
24
+ value: Whether the version flag was passed.
25
+
26
+ Raises:
27
+ typer.Exit: If the version flag was passed.
28
+ """
29
+ if value:
30
+ console.print(constants.VERSION)
31
+ raise typer.Exit()
32
+
33
+
34
+ @cli.callback()
35
+ def main(
36
+ version: bool = typer.Option(
37
+ None,
38
+ "-v",
39
+ "--version",
40
+ callback=version,
41
+ help="Get the Reflex version.",
42
+ is_eager=True,
43
+ ),
44
+ ):
45
+ """Reflex CLI to create, run, and deploy apps."""
46
+ pass
25
47
 
26
48
 
27
49
  @cli.command()
28
50
  def init(
29
- name: str = typer.Option(None, help="Name of the app to be initialized."),
51
+ name: str = typer.Option(
52
+ None, metavar="APP_NAME", help="The name of the app to be initialized."
53
+ ),
30
54
  template: constants.Template = typer.Option(
31
- constants.Template.DEFAULT, help="Template to use for the app."
55
+ constants.Template.DEFAULT, help="The template to initialize the app with."
56
+ ),
57
+ loglevel: constants.LogLevel = typer.Option(
58
+ constants.LogLevel.INFO, help="The log level to use."
32
59
  ),
33
60
  ):
34
61
  """Initialize a new Reflex app in the current directory."""
35
- app_name = prerequisites.get_default_app_name() if name is None else name
36
-
37
- # Make sure they don't name the app "reflex".
38
- if app_name == constants.MODULE_NAME:
39
- console.print(
40
- f"[red]The app directory cannot be named [bold]{constants.MODULE_NAME}."
41
- )
42
- raise typer.Exit()
62
+ # Set the log level.
63
+ console.set_log_level(loglevel)
43
64
 
65
+ # Get the app name.
66
+ app_name = prerequisites.get_default_app_name() if name is None else name
44
67
  console.rule(f"[bold]Initializing {app_name}")
45
- # Set up the web directory.
46
- prerequisites.validate_and_install_bun()
47
- prerequisites.initialize_web_directory()
68
+
69
+ # Set up the web project.
70
+ prerequisites.initialize_frontend_dependencies()
48
71
 
49
72
  # Migrate Pynecone projects to Reflex.
50
73
  prerequisites.migrate_to_reflex()
51
74
 
52
75
  # Set up the app directory, only if the config doesn't exist.
76
+ config = get_config()
53
77
  if not os.path.exists(constants.CONFIG_FILE):
54
78
  prerequisites.create_config(app_name)
55
79
  prerequisites.initialize_app_directory(app_name, template)
56
80
  build.set_reflex_project_hash()
57
- telemetry.send("init", get_config().telemetry_enabled)
81
+ telemetry.send("init", config.telemetry_enabled)
58
82
  else:
59
- build.set_reflex_project_hash()
60
- telemetry.send("reinit", get_config().telemetry_enabled)
83
+ telemetry.send("reinit", config.telemetry_enabled)
61
84
 
62
85
  # Initialize the .gitignore.
63
86
  prerequisites.initialize_gitignore()
87
+
64
88
  # Finish initializing the app.
65
- console.log(f"[bold green]Finished Initializing: {app_name}")
89
+ console.success(f"Initialized {app_name}")
66
90
 
67
91
 
68
92
  @cli.command()
@@ -74,18 +98,17 @@ def run(
74
98
  False, "--frontend-only", help="Execute only frontend."
75
99
  ),
76
100
  backend: bool = typer.Option(False, "--backend-only", help="Execute only backend."),
77
- loglevel: constants.LogLevel = typer.Option(
78
- constants.LogLevel.ERROR, help="The log level to use."
79
- ),
80
101
  frontend_port: str = typer.Option(None, help="Specify a different frontend port."),
81
102
  backend_port: str = typer.Option(None, help="Specify a different backend port."),
82
103
  backend_host: str = typer.Option(None, help="Specify the backend host."),
104
+ loglevel: constants.LogLevel = typer.Option(
105
+ constants.LogLevel.INFO, help="The log level to use."
106
+ ),
83
107
  ):
84
108
  """Run the app in the current directory."""
85
- if platform.system() == "Windows":
86
- console.print(
87
- "[yellow][WARNING] We strongly advise you to use Windows Subsystem for Linux (WSL) for optimal performance when using Reflex. Due to compatibility issues with one of our dependencies, Bun, you may experience slower performance on Windows. By using WSL, you can expect to see a significant speed increase."
88
- )
109
+ # Set the log level.
110
+ console.set_log_level(loglevel)
111
+
89
112
  # Set ports as os env variables to take precedence over config and
90
113
  # .env variables(if override_os_envs flag in config is set to False).
91
114
  build.set_os_env(
@@ -94,17 +117,20 @@ def run(
94
117
  backend_host=backend_host,
95
118
  )
96
119
 
97
- frontend_port = (
98
- get_config().frontend_port if frontend_port is None else frontend_port
99
- )
100
- backend_port = get_config().backend_port if backend_port is None else backend_port
101
- backend_host = get_config().backend_host if backend_host is None else backend_host
120
+ # Get the ports from the config.
121
+ config = get_config()
122
+ frontend_port = config.frontend_port if frontend_port is None else frontend_port
123
+ backend_port = config.backend_port if backend_port is None else backend_port
124
+ backend_host = config.backend_host if backend_host is None else backend_host
102
125
 
103
126
  # If no --frontend-only and no --backend-only, then turn on frontend and backend both
104
127
  if not frontend and not backend:
105
128
  frontend = True
106
129
  backend = True
107
130
 
131
+ # Check that the app is initialized.
132
+ prerequisites.check_initialized(frontend=frontend)
133
+
108
134
  # If something is running on the ports, ask the user if they want to kill or change it.
109
135
  if frontend and processes.is_process_on_port(frontend_port):
110
136
  frontend_port = processes.change_or_terminate_port(frontend_port, "frontend")
@@ -112,20 +138,6 @@ def run(
112
138
  if backend and processes.is_process_on_port(backend_port):
113
139
  backend_port = processes.change_or_terminate_port(backend_port, "backend")
114
140
 
115
- # Check that the app is initialized.
116
- if frontend and not prerequisites.is_initialized():
117
- console.print(
118
- "[red]The app is not initialized. Run [bold]reflex init[/bold] first."
119
- )
120
- raise typer.Exit()
121
-
122
- # Check that the template is up to date.
123
- if frontend and not prerequisites.is_latest_template():
124
- console.print(
125
- "[red]The base app template has updated. Run [bold]reflex init[/bold] again."
126
- )
127
- raise typer.Exit()
128
-
129
141
  # Get the app module.
130
142
  console.rule("[bold]Starting Reflex App")
131
143
  app = prerequisites.get_app()
@@ -153,18 +165,16 @@ def run(
153
165
  assert setup_frontend and frontend_cmd and backend_cmd, "Invalid env"
154
166
 
155
167
  # Post a telemetry event.
156
- telemetry.send(f"run-{env.value}", get_config().telemetry_enabled)
168
+ telemetry.send(f"run-{env.value}", config.telemetry_enabled)
157
169
 
158
170
  # Run the frontend and backend.
159
171
  if frontend:
160
- setup_frontend(Path.cwd(), loglevel)
161
- threading.Thread(
162
- target=frontend_cmd, args=(Path.cwd(), frontend_port, loglevel)
163
- ).start()
172
+ setup_frontend(Path.cwd())
173
+ threading.Thread(target=frontend_cmd, args=(Path.cwd(), frontend_port)).start()
164
174
  if backend:
165
175
  threading.Thread(
166
176
  target=backend_cmd,
167
- args=(app.__name__, backend_host, backend_port, loglevel),
177
+ args=(app.__name__, backend_host, backend_port),
168
178
  ).start()
169
179
 
170
180
  # Display custom message when there is a keyboard interrupt.
@@ -176,7 +186,6 @@ def deploy(dry_run: bool = typer.Option(False, help="Whether to run a dry run.")
176
186
  """Deploy the app to the Reflex hosting service."""
177
187
  # Get the app config.
178
188
  config = get_config()
179
- config.api_url = prerequisites.get_production_backend_url()
180
189
 
181
190
  # Check if the deploy url is set.
182
191
  if config.rxdeploy_url is None:
@@ -185,7 +194,7 @@ def deploy(dry_run: bool = typer.Option(False, help="Whether to run a dry run.")
185
194
 
186
195
  # Compile the app in production mode.
187
196
  typer.echo("Compiling production app")
188
- export(for_reflex_deploy=True)
197
+ export()
189
198
 
190
199
  # Exit early if this is a dry run.
191
200
  if dry_run:
@@ -217,29 +226,29 @@ def export(
217
226
  backend: bool = typer.Option(
218
227
  True, "--frontend-only", help="Export only frontend.", show_default=False
219
228
  ),
220
- for_reflex_deploy: bool = typer.Option(
221
- False,
222
- "--for-reflex-deploy",
223
- help="Whether export the app for Reflex Deploy Service.",
229
+ loglevel: constants.LogLevel = typer.Option(
230
+ constants.LogLevel.INFO, help="The log level to use."
224
231
  ),
225
232
  ):
226
233
  """Export the app to a zip file."""
227
- config = get_config()
234
+ # Set the log level.
235
+ console.set_log_level(loglevel)
228
236
 
229
- if for_reflex_deploy:
230
- # Get the app config and modify the api_url base on username and app_name.
231
- config.api_url = prerequisites.get_production_backend_url()
237
+ # Check that the app is initialized.
238
+ prerequisites.check_initialized(frontend=frontend)
232
239
 
233
240
  # Compile the app in production mode and export it.
234
241
  console.rule("[bold]Compiling production app and preparing for export.")
235
242
 
236
243
  if frontend:
237
- # ensure module can be imported and app.compile() is called
244
+ # Ensure module can be imported and app.compile() is called.
238
245
  prerequisites.get_app()
239
- # set up .web directory and install frontend dependencies
246
+ # Set up .web directory and install frontend dependencies.
240
247
  build.setup_frontend(Path.cwd())
241
248
 
242
- build.export_app(
249
+ # Export the app.
250
+ config = get_config()
251
+ build.export(
243
252
  backend=backend,
244
253
  frontend=frontend,
245
254
  zip=zipping,
@@ -247,15 +256,15 @@ def export(
247
256
  )
248
257
 
249
258
  # Post a telemetry event.
250
- telemetry.send("export", get_config().telemetry_enabled)
259
+ telemetry.send("export", config.telemetry_enabled)
251
260
 
252
261
  if zipping:
253
- console.rule(
262
+ console.log(
254
263
  """Backend & Frontend compiled. See [green bold]backend.zip[/green bold]
255
264
  and [green bold]frontend.zip[/green bold]."""
256
265
  )
257
266
  else:
258
- console.rule(
267
+ console.log(
259
268
  """Backend & Frontend compiled. See [green bold]app[/green bold]
260
269
  and [green bold].web/_static[/green bold] directories."""
261
270
  )
@@ -267,16 +276,22 @@ db_cli = typer.Typer()
267
276
  @db_cli.command(name="init")
268
277
  def db_init():
269
278
  """Create database schema and migration configuration."""
279
+ # Check the database url.
270
280
  if get_config().db_url is None:
271
- console.print("[red]db_url is not configured, cannot initialize.")
281
+ console.error("db_url is not configured, cannot initialize.")
282
+ return
283
+
284
+ # Check the alembic config.
272
285
  if Path(constants.ALEMBIC_CONFIG).exists():
273
- console.print(
274
- "[red]Database is already initialized. Use "
286
+ console.error(
287
+ "Database is already initialized. Use "
275
288
  "[bold]reflex db makemigrations[/bold] to create schema change "
276
289
  "scripts and [bold]reflex db migrate[/bold] to apply migrations "
277
290
  "to a new or existing database.",
278
291
  )
279
292
  return
293
+
294
+ # Initialize the database.
280
295
  prerequisites.get_app()
281
296
  model.Model.alembic_init()
282
297
  model.Model.migrate(autogenerate=True)
@@ -308,13 +323,12 @@ def makemigrations(
308
323
  except CommandError as command_error:
309
324
  if "Target database is not up to date." not in str(command_error):
310
325
  raise
311
- console.print(
312
- f"[red]{command_error} Run [bold]reflex db migrate[/bold] to update database."
326
+ console.error(
327
+ f"{command_error} Run [bold]reflex db migrate[/bold] to update database."
313
328
  )
314
329
 
315
330
 
316
- cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema")
317
- main = cli
331
+ cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
318
332
 
319
333
  if __name__ == "__main__":
320
- main()
334
+ cli()
reflex/route.py CHANGED
@@ -7,8 +7,8 @@ from typing import Dict, List, Optional, Union
7
7
 
8
8
  from reflex import constants
9
9
  from reflex.event import EventHandler
10
-
11
- DECORATED_ROUTES = []
10
+ from reflex.page import page
11
+ from reflex.utils.console import deprecate
12
12
 
13
13
 
14
14
  def route(
@@ -37,25 +37,15 @@ def route(
37
37
  Returns:
38
38
  The decorated function.
39
39
  """
40
-
41
- def decorator(render_fn):
42
- kwargs = {}
43
- if route:
44
- kwargs["route"] = route
45
- if title:
46
- kwargs["title"] = title
47
- if image:
48
- kwargs["image"] = image
49
- if description:
50
- kwargs["description"] = description
51
- if on_load:
52
- kwargs["on_load"] = on_load
53
-
54
- DECORATED_ROUTES.append((render_fn, kwargs))
55
-
56
- return render_fn
57
-
58
- return decorator
40
+ deprecate("@rx.route is deprecated and is being replaced by @rx.page instead")
41
+
42
+ return page(
43
+ route=route,
44
+ title=title,
45
+ image=image,
46
+ description=description,
47
+ on_load=on_load,
48
+ )
59
49
 
60
50
 
61
51
  def verify_route_validity(route: str) -> None:
reflex/state.py CHANGED
@@ -476,13 +476,19 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
476
476
  """
477
477
  return self.router_data.get(constants.RouteVar.CLIENT_IP, "")
478
478
 
479
- def get_current_page(self) -> str:
479
+ def get_current_page(self, origin=False) -> str:
480
480
  """Obtain the path of current page from the router data.
481
481
 
482
+ Args:
483
+ origin: whether to return the base route as shown in browser
484
+
482
485
  Returns:
483
486
  The current page.
484
487
  """
485
- return self.router_data.get(constants.RouteVar.PATH, "")
488
+ if origin:
489
+ return self.router_data.get(constants.RouteVar.ORIGIN, "")
490
+ else:
491
+ return self.router_data.get(constants.RouteVar.PATH, "")
486
492
 
487
493
  def get_query_params(self) -> Dict[str, str]:
488
494
  """Obtain the query parameters for the queried page.
@@ -595,6 +601,10 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
595
601
  self.mark_dirty()
596
602
  return
597
603
 
604
+ # Make sure lists and dicts are converted to ReflexList and ReflexDict.
605
+ if name in self.vars and types._isinstance(value, Union[List, Dict]):
606
+ value = _convert_mutable_datatypes(value, self._reassign_field, name)
607
+
598
608
  # Set the attribute.
599
609
  super().__setattr__(name, value)
600
610
 
@@ -730,8 +740,14 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
730
740
 
731
741
  # Handle regular generators.
732
742
  elif inspect.isgenerator(events):
733
- for event in events:
734
- yield event, False
743
+ try:
744
+ while True:
745
+ yield next(events), False
746
+ except StopIteration as si:
747
+ # the "return" value of the generator is not available
748
+ # in the loop, we must catch StopIteration to access it
749
+ if si.value is not None:
750
+ yield si.value, False
735
751
  yield None, True
736
752
 
737
753
  # Handle regular event chains.
@@ -984,21 +1000,22 @@ def _convert_mutable_datatypes(
984
1000
  The converted field_value
985
1001
  """
986
1002
  if isinstance(field_value, list):
987
- for index in range(len(field_value)):
988
- field_value[index] = _convert_mutable_datatypes(
989
- field_value[index], reassign_field, field_name
990
- )
1003
+ field_value = [
1004
+ _convert_mutable_datatypes(value, reassign_field, field_name)
1005
+ for value in field_value
1006
+ ]
991
1007
 
992
1008
  field_value = ReflexList(
993
1009
  field_value, reassign_field=reassign_field, field_name=field_name
994
1010
  )
995
1011
 
996
1012
  if isinstance(field_value, dict):
997
- for key, value in field_value.items():
998
- field_value[key] = _convert_mutable_datatypes(
999
- value, reassign_field, field_name
1000
- )
1013
+ field_value = {
1014
+ key: _convert_mutable_datatypes(value, reassign_field, field_name)
1015
+ for key, value in field_value.items()
1016
+ }
1001
1017
  field_value = ReflexDict(
1002
1018
  field_value, reassign_field=reassign_field, field_name=field_name
1003
1019
  )
1020
+
1004
1021
  return field_value
reflex/testing.py CHANGED
@@ -65,6 +65,7 @@ if platform.system == "Windows":
65
65
  else:
66
66
  FRONTEND_POPEN_ARGS["start_new_session"] = True
67
67
 
68
+
68
69
  # borrowed from py3.11
69
70
  class chdir(contextlib.AbstractContextManager):
70
71
  """Non thread-safe context manager to change the current working directory."""
@@ -150,6 +151,7 @@ class AppHarness:
150
151
  reflex.reflex.init(
151
152
  name=self.app_name,
152
153
  template=reflex.constants.Template.DEFAULT,
154
+ loglevel=reflex.constants.LogLevel.INFO,
153
155
  )
154
156
  self.app_module_path.write_text(source_code)
155
157
  with chdir(self.app_path):
@@ -179,7 +181,7 @@ class AppHarness:
179
181
  frontend_env = os.environ.copy()
180
182
  frontend_env["PORT"] = "0"
181
183
  self.frontend_process = subprocess.Popen(
182
- [reflex.utils.prerequisites.get_package_manager(), "run", "dev"],
184
+ [reflex.utils.prerequisites.get_install_package_manager(), "run", "dev"],
183
185
  stdout=subprocess.PIPE,
184
186
  encoding="utf-8",
185
187
  cwd=self.app_path / reflex.constants.WEB_DIR,