reflex 0.6.3a4__py3-none-any.whl → 0.6.4__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.

Files changed (96) hide show
  1. reflex/.templates/jinja/web/pages/_app.js.jinja2 +2 -2
  2. reflex/.templates/jinja/web/utils/context.js.jinja2 +3 -1
  3. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -3
  4. reflex/.templates/web/components/shiki/code.js +29 -0
  5. reflex/.templates/web/jsconfig.json +2 -1
  6. reflex/.templates/web/utils/state.js +6 -4
  7. reflex/__init__.py +6 -3
  8. reflex/__init__.pyi +4 -3
  9. reflex/app.py +6 -5
  10. reflex/compiler/compiler.py +6 -7
  11. reflex/compiler/utils.py +8 -1
  12. reflex/components/base/error_boundary.py +37 -24
  13. reflex/components/base/error_boundary.pyi +8 -7
  14. reflex/components/component.py +9 -4
  15. reflex/components/core/banner.py +2 -2
  16. reflex/components/core/client_side_routing.py +1 -1
  17. reflex/components/core/clipboard.py +1 -1
  18. reflex/components/core/clipboard.pyi +1 -1
  19. reflex/components/core/cond.py +1 -1
  20. reflex/components/core/debounce.py +5 -1
  21. reflex/components/core/upload.py +7 -9
  22. reflex/components/core/upload.pyi +2 -2
  23. reflex/components/datadisplay/code.py +1 -1
  24. reflex/components/datadisplay/dataeditor.py +83 -18
  25. reflex/components/datadisplay/dataeditor.pyi +67 -15
  26. reflex/components/datadisplay/shiki_code_block.py +813 -0
  27. reflex/components/datadisplay/shiki_code_block.pyi +2211 -0
  28. reflex/components/dynamic.py +3 -3
  29. reflex/components/el/elements/forms.py +37 -23
  30. reflex/components/el/elements/forms.pyi +7 -4
  31. reflex/components/markdown/markdown.py +12 -1
  32. reflex/components/moment/moment.pyi +1 -1
  33. reflex/components/radix/primitives/drawer.pyi +2 -2
  34. reflex/components/radix/themes/base.py +2 -2
  35. reflex/components/radix/themes/color_mode.pyi +1 -1
  36. reflex/components/radix/themes/components/alert_dialog.pyi +1 -1
  37. reflex/components/radix/themes/components/checkbox.pyi +3 -3
  38. reflex/components/radix/themes/components/context_menu.pyi +1 -1
  39. reflex/components/radix/themes/components/dialog.pyi +2 -2
  40. reflex/components/radix/themes/components/dropdown_menu.pyi +2 -2
  41. reflex/components/radix/themes/components/hover_card.pyi +2 -2
  42. reflex/components/radix/themes/components/popover.pyi +1 -1
  43. reflex/components/radix/themes/components/radio_cards.pyi +1 -1
  44. reflex/components/radix/themes/components/radio_group.pyi +1 -1
  45. reflex/components/radix/themes/components/select.pyi +6 -6
  46. reflex/components/radix/themes/components/switch.pyi +1 -1
  47. reflex/components/radix/themes/components/tabs.pyi +2 -2
  48. reflex/components/radix/themes/components/tooltip.pyi +4 -2
  49. reflex/components/react_player/__init__.py +1 -0
  50. reflex/components/react_player/audio.pyi +6 -3
  51. reflex/components/react_player/react_player.py +12 -1
  52. reflex/components/react_player/react_player.pyi +11 -3
  53. reflex/components/react_player/video.pyi +6 -3
  54. reflex/components/recharts/recharts.py +2 -2
  55. reflex/components/sonner/toast.py +1 -1
  56. reflex/components/suneditor/editor.py +40 -16
  57. reflex/components/suneditor/editor.pyi +15 -11
  58. reflex/config.py +284 -20
  59. reflex/constants/__init__.py +2 -0
  60. reflex/constants/base.py +53 -31
  61. reflex/constants/compiler.py +2 -12
  62. reflex/constants/config.py +1 -2
  63. reflex/constants/installer.py +88 -32
  64. reflex/constants/style.py +1 -1
  65. reflex/constants/utils.py +32 -0
  66. reflex/custom_components/custom_components.py +3 -3
  67. reflex/event.py +152 -84
  68. reflex/experimental/__init__.py +2 -0
  69. reflex/experimental/client_state.py +1 -1
  70. reflex/experimental/layout.pyi +1 -1
  71. reflex/istate/storage.py +144 -0
  72. reflex/model.py +8 -11
  73. reflex/reflex.py +18 -17
  74. reflex/state.py +89 -151
  75. reflex/style.py +1 -1
  76. reflex/testing.py +2 -1
  77. reflex/utils/build.py +0 -12
  78. reflex/utils/exceptions.py +8 -0
  79. reflex/utils/exec.py +22 -4
  80. reflex/utils/imports.py +6 -0
  81. reflex/utils/net.py +2 -4
  82. reflex/utils/path_ops.py +7 -21
  83. reflex/utils/prerequisites.py +11 -17
  84. reflex/utils/pyi_generator.py +91 -3
  85. reflex/utils/registry.py +2 -6
  86. reflex/utils/types.py +14 -0
  87. reflex/vars/base.py +453 -424
  88. reflex/vars/function.py +6 -16
  89. reflex/vars/number.py +46 -67
  90. reflex/vars/object.py +1 -31
  91. reflex/vars/sequence.py +177 -47
  92. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/METADATA +1 -1
  93. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/RECORD +96 -91
  94. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/LICENSE +0 -0
  95. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/WHEEL +0 -0
  96. {reflex-0.6.3a4.dist-info → reflex-0.6.4.dist-info}/entry_points.txt +0 -0
@@ -3,11 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import enum
6
- from typing import Dict, List, Literal, Optional, Union
6
+ from typing import Dict, List, Literal, Optional, Tuple, Union
7
7
 
8
8
  from reflex.base import Base
9
9
  from reflex.components.component import Component, NoSSRComponent
10
- from reflex.event import EventHandler
10
+ from reflex.event import EventHandler, empty_event, identity_event
11
11
  from reflex.utils.format import to_camel_case
12
12
  from reflex.utils.imports import ImportDict, ImportVar
13
13
  from reflex.vars.base import Var
@@ -68,6 +68,35 @@ class EditorOptions(Base):
68
68
  button_list: Optional[List[Union[List[str], str]]]
69
69
 
70
70
 
71
+ def on_blur_spec(e: Var, content: Var[str]) -> Tuple[Var[str]]:
72
+ """A helper function to specify the on_blur event handler.
73
+
74
+ Args:
75
+ e: The event.
76
+ content: The content of the editor.
77
+
78
+ Returns:
79
+ A tuple containing the content of the editor.
80
+ """
81
+ return (content,)
82
+
83
+
84
+ def on_paste_spec(
85
+ e: Var, clean_data: Var[str], max_char_count: Var[bool]
86
+ ) -> Tuple[Var[str], Var[bool]]:
87
+ """A helper function to specify the on_paste event handler.
88
+
89
+ Args:
90
+ e: The event.
91
+ clean_data: The clean data.
92
+ max_char_count: The maximum character count.
93
+
94
+ Returns:
95
+ A tuple containing the clean data and the maximum character count.
96
+ """
97
+ return (clean_data, max_char_count)
98
+
99
+
71
100
  class Editor(NoSSRComponent):
72
101
  """A Rich Text Editor component based on SunEditor.
73
102
  Not every JS prop is listed here (some are not easily usable from python),
@@ -178,36 +207,31 @@ class Editor(NoSSRComponent):
178
207
  disable_toolbar: Var[bool]
179
208
 
180
209
  # Fired when the editor content changes.
181
- on_change: EventHandler[lambda content: [content]]
210
+ on_change: EventHandler[identity_event(str)]
182
211
 
183
212
  # Fired when the something is inputted in the editor.
184
- on_input: EventHandler[lambda e: [e]]
213
+ on_input: EventHandler[empty_event]
185
214
 
186
215
  # Fired when the editor loses focus.
187
- on_blur: EventHandler[lambda e, content: [content]]
216
+ on_blur: EventHandler[on_blur_spec]
188
217
 
189
218
  # Fired when the editor is loaded.
190
- on_load: EventHandler[lambda reload: [reload]]
191
-
192
- # Fired when the editor is resized.
193
- on_resize_editor: EventHandler[lambda height, prev_height: [height, prev_height]]
219
+ on_load: EventHandler[identity_event(bool)]
194
220
 
195
221
  # Fired when the editor content is copied.
196
- on_copy: EventHandler[lambda e, clipboard_data: [clipboard_data]]
222
+ on_copy: EventHandler[empty_event]
197
223
 
198
224
  # Fired when the editor content is cut.
199
- on_cut: EventHandler[lambda e, clipboard_data: [clipboard_data]]
225
+ on_cut: EventHandler[empty_event]
200
226
 
201
227
  # Fired when the editor content is pasted.
202
- on_paste: EventHandler[
203
- lambda e, clean_data, max_char_count: [clean_data, max_char_count]
204
- ]
228
+ on_paste: EventHandler[on_paste_spec]
205
229
 
206
230
  # Fired when the code view is toggled.
207
- toggle_code_view: EventHandler[lambda is_code_view: [is_code_view]]
231
+ toggle_code_view: EventHandler[identity_event(bool)]
208
232
 
209
233
  # Fired when the full screen mode is toggled.
210
- toggle_full_screen: EventHandler[lambda is_full_screen: [is_full_screen]]
234
+ toggle_full_screen: EventHandler[identity_event(bool)]
211
235
 
212
236
  def add_imports(self) -> ImportDict:
213
237
  """Add imports for the Editor component.
@@ -4,7 +4,7 @@
4
4
  # This file was generated by `reflex/utils/pyi_generator.py`!
5
5
  # ------------------------------------------------------
6
6
  import enum
7
- from typing import Any, Dict, List, Literal, Optional, Union, overload
7
+ from typing import Any, Dict, List, Literal, Optional, Tuple, Union, overload
8
8
 
9
9
  from reflex.base import Base
10
10
  from reflex.components.component import NoSSRComponent
@@ -44,6 +44,11 @@ class EditorOptions(Base):
44
44
  rtl: Optional[bool]
45
45
  button_list: Optional[List[Union[List[str], str]]]
46
46
 
47
+ def on_blur_spec(e: Var, content: Var[str]) -> Tuple[Var[str]]: ...
48
+ def on_paste_spec(
49
+ e: Var, clean_data: Var[str], max_char_count: Var[bool]
50
+ ) -> Tuple[Var[str], Var[bool]]: ...
51
+
47
52
  class Editor(NoSSRComponent):
48
53
  def add_imports(self) -> ImportDict: ...
49
54
  @overload
@@ -122,16 +127,16 @@ class Editor(NoSSRComponent):
122
127
  class_name: Optional[Any] = None,
123
128
  autofocus: Optional[bool] = None,
124
129
  custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
125
- on_blur: Optional[EventType] = None,
126
- on_change: Optional[EventType] = None,
130
+ on_blur: Optional[EventType[str]] = None,
131
+ on_change: Optional[EventType[str]] = None,
127
132
  on_click: Optional[EventType[[]]] = None,
128
133
  on_context_menu: Optional[EventType[[]]] = None,
129
- on_copy: Optional[EventType] = None,
130
- on_cut: Optional[EventType] = None,
134
+ on_copy: Optional[EventType[[]]] = None,
135
+ on_cut: Optional[EventType[[]]] = None,
131
136
  on_double_click: Optional[EventType[[]]] = None,
132
137
  on_focus: Optional[EventType[[]]] = None,
133
- on_input: Optional[EventType] = None,
134
- on_load: Optional[EventType] = None,
138
+ on_input: Optional[EventType[[]]] = None,
139
+ on_load: Optional[EventType[bool]] = None,
135
140
  on_mount: Optional[EventType[[]]] = None,
136
141
  on_mouse_down: Optional[EventType[[]]] = None,
137
142
  on_mouse_enter: Optional[EventType[[]]] = None,
@@ -140,12 +145,11 @@ class Editor(NoSSRComponent):
140
145
  on_mouse_out: Optional[EventType[[]]] = None,
141
146
  on_mouse_over: Optional[EventType[[]]] = None,
142
147
  on_mouse_up: Optional[EventType[[]]] = None,
143
- on_paste: Optional[EventType] = None,
144
- on_resize_editor: Optional[EventType] = None,
148
+ on_paste: Optional[EventType[str, bool]] = None,
145
149
  on_scroll: Optional[EventType[[]]] = None,
146
150
  on_unmount: Optional[EventType[[]]] = None,
147
- toggle_code_view: Optional[EventType] = None,
148
- toggle_full_screen: Optional[EventType] = None,
151
+ toggle_code_view: Optional[EventType[bool]] = None,
152
+ toggle_full_screen: Optional[EventType[bool]] = None,
149
153
  **props,
150
154
  ) -> "Editor":
151
155
  """Create an instance of Editor. No children allowed.
reflex/config.py CHANGED
@@ -2,14 +2,20 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import dataclasses
6
+ import enum
5
7
  import importlib
8
+ import inspect
6
9
  import os
7
10
  import sys
8
11
  import urllib.parse
9
12
  from pathlib import Path
10
- from typing import Any, Dict, List, Optional, Set, Union
13
+ from typing import Any, Dict, List, Optional, Set
11
14
 
12
- from reflex.utils.exceptions import ConfigError
15
+ from typing_extensions import Annotated, get_type_hints
16
+
17
+ from reflex.utils.exceptions import ConfigError, EnvironmentVarValueError
18
+ from reflex.utils.types import GenericType, is_union, value_inside_optional
13
19
 
14
20
  try:
15
21
  import pydantic.v1 as pydantic
@@ -131,6 +137,258 @@ class DBConfig(Base):
131
137
  return f"{self.engine}://{path}/{self.database}"
132
138
 
133
139
 
140
+ def get_default_value_for_field(field: dataclasses.Field) -> Any:
141
+ """Get the default value for a field.
142
+
143
+ Args:
144
+ field: The field.
145
+
146
+ Returns:
147
+ The default value.
148
+
149
+ Raises:
150
+ ValueError: If no default value is found.
151
+ """
152
+ if field.default != dataclasses.MISSING:
153
+ return field.default
154
+ elif field.default_factory != dataclasses.MISSING:
155
+ return field.default_factory()
156
+ else:
157
+ raise ValueError(
158
+ f"Missing value for environment variable {field.name} and no default value found"
159
+ )
160
+
161
+
162
+ # TODO: Change all interpret_.* signatures to value: str, field: dataclasses.Field once we migrate rx.Config to dataclasses
163
+ def interpret_boolean_env(value: str, field_name: str) -> bool:
164
+ """Interpret a boolean environment variable value.
165
+
166
+ Args:
167
+ value: The environment variable value.
168
+ field_name: The field name.
169
+
170
+ Returns:
171
+ The interpreted value.
172
+
173
+ Raises:
174
+ EnvironmentVarValueError: If the value is invalid.
175
+ """
176
+ true_values = ["true", "1", "yes", "y"]
177
+ false_values = ["false", "0", "no", "n"]
178
+
179
+ if value.lower() in true_values:
180
+ return True
181
+ elif value.lower() in false_values:
182
+ return False
183
+ raise EnvironmentVarValueError(f"Invalid boolean value: {value} for {field_name}")
184
+
185
+
186
+ def interpret_int_env(value: str, field_name: str) -> int:
187
+ """Interpret an integer environment variable value.
188
+
189
+ Args:
190
+ value: The environment variable value.
191
+ field_name: The field name.
192
+
193
+ Returns:
194
+ The interpreted value.
195
+
196
+ Raises:
197
+ EnvironmentVarValueError: If the value is invalid.
198
+ """
199
+ try:
200
+ return int(value)
201
+ except ValueError as ve:
202
+ raise EnvironmentVarValueError(
203
+ f"Invalid integer value: {value} for {field_name}"
204
+ ) from ve
205
+
206
+
207
+ def interpret_existing_path_env(value: str, field_name: str) -> ExistingPath:
208
+ """Interpret a path environment variable value as an existing path.
209
+
210
+ Args:
211
+ value: The environment variable value.
212
+ field_name: The field name.
213
+
214
+ Returns:
215
+ The interpreted value.
216
+
217
+ Raises:
218
+ EnvironmentVarValueError: If the path does not exist.
219
+ """
220
+ path = Path(value)
221
+ if not path.exists():
222
+ raise EnvironmentVarValueError(f"Path does not exist: {path} for {field_name}")
223
+ return path
224
+
225
+
226
+ def interpret_path_env(value: str, field_name: str) -> Path:
227
+ """Interpret a path environment variable value.
228
+
229
+ Args:
230
+ value: The environment variable value.
231
+ field_name: The field name.
232
+
233
+ Returns:
234
+ The interpreted value.
235
+ """
236
+ return Path(value)
237
+
238
+
239
+ def interpret_enum_env(value: str, field_type: GenericType, field_name: str) -> Any:
240
+ """Interpret an enum environment variable value.
241
+
242
+ Args:
243
+ value: The environment variable value.
244
+ field_type: The field type.
245
+ field_name: The field name.
246
+
247
+ Returns:
248
+ The interpreted value.
249
+
250
+ Raises:
251
+ EnvironmentVarValueError: If the value is invalid.
252
+ """
253
+ try:
254
+ return field_type(value)
255
+ except ValueError as ve:
256
+ raise EnvironmentVarValueError(
257
+ f"Invalid enum value: {value} for {field_name}"
258
+ ) from ve
259
+
260
+
261
+ def interpret_env_var_value(
262
+ value: str, field_type: GenericType, field_name: str
263
+ ) -> Any:
264
+ """Interpret an environment variable value based on the field type.
265
+
266
+ Args:
267
+ value: The environment variable value.
268
+ field_type: The field type.
269
+ field_name: The field name.
270
+
271
+ Returns:
272
+ The interpreted value.
273
+
274
+ Raises:
275
+ ValueError: If the value is invalid.
276
+ """
277
+ field_type = value_inside_optional(field_type)
278
+
279
+ if is_union(field_type):
280
+ raise ValueError(
281
+ f"Union types are not supported for environment variables: {field_name}."
282
+ )
283
+
284
+ if field_type is bool:
285
+ return interpret_boolean_env(value, field_name)
286
+ elif field_type is str:
287
+ return value
288
+ elif field_type is int:
289
+ return interpret_int_env(value, field_name)
290
+ elif field_type is Path:
291
+ return interpret_path_env(value, field_name)
292
+ elif field_type is ExistingPath:
293
+ return interpret_existing_path_env(value, field_name)
294
+ elif inspect.isclass(field_type) and issubclass(field_type, enum.Enum):
295
+ return interpret_enum_env(value, field_type, field_name)
296
+
297
+ else:
298
+ raise ValueError(
299
+ f"Invalid type for environment variable {field_name}: {field_type}. This is probably an issue in Reflex."
300
+ )
301
+
302
+
303
+ class PathExistsFlag:
304
+ """Flag to indicate that a path must exist."""
305
+
306
+
307
+ ExistingPath = Annotated[Path, PathExistsFlag]
308
+
309
+
310
+ @dataclasses.dataclass(init=False)
311
+ class EnvironmentVariables:
312
+ """Environment variables class to instantiate environment variables."""
313
+
314
+ # Whether to use npm over bun to install frontend packages.
315
+ REFLEX_USE_NPM: bool = False
316
+
317
+ # The npm registry to use.
318
+ NPM_CONFIG_REGISTRY: Optional[str] = None
319
+
320
+ # Whether to use Granian for the backend. Otherwise, use Uvicorn.
321
+ REFLEX_USE_GRANIAN: bool = False
322
+
323
+ # The username to use for authentication on python package repository. Username and password must both be provided.
324
+ TWINE_USERNAME: Optional[str] = None
325
+
326
+ # The password to use for authentication on python package repository. Username and password must both be provided.
327
+ TWINE_PASSWORD: Optional[str] = None
328
+
329
+ # Whether to use the system installed bun. If set to false, bun will be bundled with the app.
330
+ REFLEX_USE_SYSTEM_BUN: bool = False
331
+
332
+ # Whether to use the system installed node and npm. If set to false, node and npm will be bundled with the app.
333
+ REFLEX_USE_SYSTEM_NODE: bool = False
334
+
335
+ # The working directory for the next.js commands.
336
+ REFLEX_WEB_WORKDIR: Path = Path(constants.Dirs.WEB)
337
+
338
+ # Path to the alembic config file
339
+ ALEMBIC_CONFIG: ExistingPath = Path(constants.ALEMBIC_CONFIG)
340
+
341
+ # Disable SSL verification for HTTPX requests.
342
+ SSL_NO_VERIFY: bool = False
343
+
344
+ # The directory to store uploaded files.
345
+ REFLEX_UPLOADED_FILES_DIR: Path = Path(constants.Dirs.UPLOADED_FILES)
346
+
347
+ # Whether to use seperate processes to compile the frontend and how many. If not set, defaults to thread executor.
348
+ REFLEX_COMPILE_PROCESSES: Optional[int] = None
349
+
350
+ # Whether to use seperate threads to compile the frontend and how many. Defaults to `min(32, os.cpu_count() + 4)`.
351
+ REFLEX_COMPILE_THREADS: Optional[int] = None
352
+
353
+ # The directory to store reflex dependencies.
354
+ REFLEX_DIR: Path = Path(constants.Reflex.DIR)
355
+
356
+ # Whether to print the SQL queries if the log level is INFO or lower.
357
+ SQLALCHEMY_ECHO: bool = False
358
+
359
+ # Whether to ignore the redis config error. Some redis servers only allow out-of-band configuration.
360
+ REFLEX_IGNORE_REDIS_CONFIG_ERROR: bool = False
361
+
362
+ # Whether to skip purging the web directory in dev mode.
363
+ REFLEX_PERSIST_WEB_DIR: bool = False
364
+
365
+ # The reflex.build frontend host.
366
+ REFLEX_BUILD_FRONTEND: str = constants.Templates.REFLEX_BUILD_FRONTEND
367
+
368
+ # The reflex.build backend host.
369
+ REFLEX_BUILD_BACKEND: str = constants.Templates.REFLEX_BUILD_BACKEND
370
+
371
+ def __init__(self):
372
+ """Initialize the environment variables."""
373
+ type_hints = get_type_hints(type(self))
374
+
375
+ for field in dataclasses.fields(self):
376
+ raw_value = os.getenv(field.name, None)
377
+
378
+ field.type = type_hints.get(field.name) or field.type
379
+
380
+ value = (
381
+ interpret_env_var_value(raw_value, field.type, field.name)
382
+ if raw_value is not None
383
+ else get_default_value_for_field(field)
384
+ )
385
+
386
+ setattr(self, field.name, value)
387
+
388
+
389
+ environment = EnvironmentVariables()
390
+
391
+
134
392
  class Config(Base):
135
393
  """The config defines runtime settings for the app.
136
394
 
@@ -191,7 +449,7 @@ class Config(Base):
191
449
  telemetry_enabled: bool = True
192
450
 
193
451
  # The bun path
194
- bun_path: Union[str, Path] = constants.Bun.DEFAULT_PATH
452
+ bun_path: ExistingPath = constants.Bun.DEFAULT_PATH
195
453
 
196
454
  # List of origins that are allowed to connect to the backend API.
197
455
  cors_allowed_origins: List[str] = ["*"]
@@ -222,6 +480,12 @@ class Config(Base):
222
480
  # Number of gunicorn workers from user
223
481
  gunicorn_workers: Optional[int] = None
224
482
 
483
+ # Number of requests before a worker is restarted
484
+ gunicorn_max_requests: int = 100
485
+
486
+ # Variance limit for max requests; gunicorn only
487
+ gunicorn_max_requests_jitter: int = 25
488
+
225
489
  # Indicate which type of state manager to use
226
490
  state_manager_mode: constants.StateManagerMode = constants.StateManagerMode.DISK
227
491
 
@@ -234,6 +498,9 @@ class Config(Base):
234
498
  # Attributes that were explicitly set by the user.
235
499
  _non_default_attributes: Set[str] = pydantic.PrivateAttr(set())
236
500
 
501
+ # Path to file containing key-values pairs to override in the environment; Dotenv format.
502
+ env_file: Optional[str] = None
503
+
237
504
  def __init__(self, *args, **kwargs):
238
505
  """Initialize the config values.
239
506
 
@@ -275,14 +542,21 @@ class Config(Base):
275
542
 
276
543
  def update_from_env(self) -> dict[str, Any]:
277
544
  """Update the config values based on set environment variables.
545
+ If there is a set env_file, it is loaded first.
278
546
 
279
547
  Returns:
280
548
  The updated config values.
281
-
282
- Raises:
283
- EnvVarValueError: If an environment variable is set to an invalid type.
284
549
  """
285
- from reflex.utils.exceptions import EnvVarValueError
550
+ if self.env_file:
551
+ try:
552
+ from dotenv import load_dotenv # type: ignore
553
+
554
+ # load env file if exists
555
+ load_dotenv(self.env_file, override=True)
556
+ except ImportError:
557
+ console.error(
558
+ """The `python-dotenv` package is required to load environment variables from a file. Run `pip install "python-dotenv>=1.0.1"`."""
559
+ )
286
560
 
287
561
  updated_values = {}
288
562
  # Iterate over the fields.
@@ -298,21 +572,11 @@ class Config(Base):
298
572
  dedupe=True,
299
573
  )
300
574
 
301
- # Convert the env var to the expected type.
302
- try:
303
- if issubclass(field.type_, bool):
304
- # special handling for bool values
305
- env_var = env_var.lower() in ["true", "1", "yes"]
306
- else:
307
- env_var = field.type_(env_var)
308
- except ValueError as ve:
309
- console.error(
310
- f"Could not convert {key.upper()}={env_var} to type {field.type_}"
311
- )
312
- raise EnvVarValueError from ve
575
+ # Interpret the value.
576
+ value = interpret_env_var_value(env_var, field.outer_type_, field.name)
313
577
 
314
578
  # Set the value.
315
- updated_values[key] = env_var
579
+ updated_values[key] = value
316
580
 
317
581
  return updated_values
318
582
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  from .base import (
4
4
  COOKIES,
5
+ ENV_BACKEND_ONLY_ENV_VAR,
6
+ ENV_FRONTEND_ONLY_ENV_VAR,
5
7
  ENV_MODE_ENV_VAR,
6
8
  IS_WINDOWS,
7
9
  LOCAL_STORAGE,
reflex/constants/base.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
6
5
  import platform
7
6
  from enum import Enum
8
7
  from importlib import metadata
@@ -11,6 +10,8 @@ from types import SimpleNamespace
11
10
 
12
11
  from platformdirs import PlatformDirs
13
12
 
13
+ from .utils import classproperty
14
+
14
15
  IS_WINDOWS = platform.system() == "Windows"
15
16
 
16
17
 
@@ -20,6 +21,8 @@ class Dirs(SimpleNamespace):
20
21
  # The frontend directories in a project.
21
22
  # The web folder where the NextJS app is compiled to.
22
23
  WEB = ".web"
24
+ # The directory where uploaded files are stored.
25
+ UPLOADED_FILES = "uploaded_files"
23
26
  # The name of the assets directory.
24
27
  APP_ASSETS = "assets"
25
28
  # The name of the assets directory for external ressource (a subfolder of APP_ASSETS).
@@ -64,21 +67,13 @@ class Reflex(SimpleNamespace):
64
67
 
65
68
  # Files and directories used to init a new project.
66
69
  # The directory to store reflex dependencies.
67
- # Get directory value from enviroment variables if it exists.
68
- _dir = os.environ.get("REFLEX_DIR", "")
69
-
70
- DIR = Path(
71
- _dir
72
- or (
73
- # on windows, we use C:/Users/<username>/AppData/Local/reflex.
74
- # on macOS, we use ~/Library/Application Support/reflex.
75
- # on linux, we use ~/.local/share/reflex.
76
- # If user sets REFLEX_DIR envroment variable use that instead.
77
- PlatformDirs(MODULE_NAME, False).user_data_dir
78
- )
79
- )
80
- # The root directory of the reflex library.
70
+ # on windows, we use C:/Users/<username>/AppData/Local/reflex.
71
+ # on macOS, we use ~/Library/Application Support/reflex.
72
+ # on linux, we use ~/.local/share/reflex.
73
+ # If user sets REFLEX_DIR envroment variable use that instead.
74
+ DIR = PlatformDirs(MODULE_NAME, False).user_data_path
81
75
 
76
+ # The root directory of the reflex library.
82
77
  ROOT_DIR = Path(__file__).parents[2]
83
78
 
84
79
  RELEASES_URL = f"https://api.github.com/repos/reflex-dev/templates/releases"
@@ -101,27 +96,51 @@ class Templates(SimpleNamespace):
101
96
  DEFAULT = "blank"
102
97
 
103
98
  # The reflex.build frontend host
104
- REFLEX_BUILD_FRONTEND = os.environ.get(
105
- "REFLEX_BUILD_FRONTEND", "https://flexgen.reflex.run"
106
- )
99
+ REFLEX_BUILD_FRONTEND = "https://flexgen.reflex.run"
107
100
 
108
101
  # The reflex.build backend host
109
- REFLEX_BUILD_BACKEND = os.environ.get(
110
- "REFLEX_BUILD_BACKEND", "https://flexgen-prod-flexgen.fly.dev"
111
- )
102
+ REFLEX_BUILD_BACKEND = "https://flexgen-prod-flexgen.fly.dev"
103
+
104
+ @classproperty
105
+ @classmethod
106
+ def REFLEX_BUILD_URL(cls):
107
+ """The URL to redirect to reflex.build.
112
108
 
113
- # The URL to redirect to reflex.build
114
- REFLEX_BUILD_URL = (
115
- REFLEX_BUILD_FRONTEND + "/gen?reflex_init_token={reflex_init_token}"
116
- )
109
+ Returns:
110
+ The URL to redirect to reflex.build.
111
+ """
112
+ from reflex.config import environment
117
113
 
118
- # The URL to poll waiting for the user to select a generation.
119
- REFLEX_BUILD_POLL_URL = REFLEX_BUILD_BACKEND + "/api/init/{reflex_init_token}"
114
+ return (
115
+ environment.REFLEX_BUILD_FRONTEND
116
+ + "/gen?reflex_init_token={reflex_init_token}"
117
+ )
120
118
 
121
- # The URL to fetch the generation's reflex code
122
- REFLEX_BUILD_CODE_URL = (
123
- REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}/refactored"
124
- )
119
+ @classproperty
120
+ @classmethod
121
+ def REFLEX_BUILD_POLL_URL(cls):
122
+ """The URL to poll waiting for the user to select a generation.
123
+
124
+ Returns:
125
+ The URL to poll waiting for the user to select a generation.
126
+ """
127
+ from reflex.config import environment
128
+
129
+ return environment.REFLEX_BUILD_BACKEND + "/api/init/{reflex_init_token}"
130
+
131
+ @classproperty
132
+ @classmethod
133
+ def REFLEX_BUILD_CODE_URL(cls):
134
+ """The URL to fetch the generation's reflex code.
135
+
136
+ Returns:
137
+ The URL to fetch the generation's reflex code.
138
+ """
139
+ from reflex.config import environment
140
+
141
+ return (
142
+ environment.REFLEX_BUILD_BACKEND + "/api/gen/{generation_hash}/refactored"
143
+ )
125
144
 
126
145
  class Dirs(SimpleNamespace):
127
146
  """Folders used by the template system of Reflex."""
@@ -226,6 +245,9 @@ SKIP_COMPILE_ENV_VAR = "__REFLEX_SKIP_COMPILE"
226
245
  # This env var stores the execution mode of the app
227
246
  ENV_MODE_ENV_VAR = "REFLEX_ENV_MODE"
228
247
 
248
+ ENV_BACKEND_ONLY_ENV_VAR = "REFLEX_BACKEND_ONLY"
249
+ ENV_FRONTEND_ONLY_ENV_VAR = "REFLEX_FRONTEND_ONLY"
250
+
229
251
  # Testing variables.
230
252
  # Testing os env set by pytest when running a test case.
231
253
  PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
@@ -114,8 +114,8 @@ class Imports(SimpleNamespace):
114
114
 
115
115
  EVENTS = {
116
116
  "react": [ImportVar(tag="useContext")],
117
- f"/{Dirs.CONTEXTS_PATH}": [ImportVar(tag="EventLoopContext")],
118
- f"/{Dirs.STATE_PATH}": [ImportVar(tag=CompileVars.TO_EVENT)],
117
+ f"$/{Dirs.CONTEXTS_PATH}": [ImportVar(tag="EventLoopContext")],
118
+ f"$/{Dirs.STATE_PATH}": [ImportVar(tag=CompileVars.TO_EVENT)],
119
119
  }
120
120
 
121
121
 
@@ -132,16 +132,6 @@ class Hooks(SimpleNamespace):
132
132
  }
133
133
  })"""
134
134
 
135
- FRONTEND_ERRORS = f"""
136
- const logFrontendError = (error, info) => {{
137
- if (process.env.NODE_ENV === "production") {{
138
- addEvents([Event("{CompileVars.FRONTEND_EXCEPTION_STATE_FULL}.handle_frontend_exception", {{
139
- stack: error.stack,
140
- }})])
141
- }}
142
- }}
143
- """
144
-
145
135
 
146
136
  class MemoizationDisposition(enum.Enum):
147
137
  """The conditions under which a component should be memoized."""