reflex 0.7.12a1__py3-none-any.whl → 0.7.13a2__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 (53) hide show
  1. reflex/.templates/jinja/app/rxconfig.py.jinja2 +1 -0
  2. reflex/.templates/web/postcss.config.js +0 -1
  3. reflex/.templates/web/utils/state.js +1 -1
  4. reflex/__init__.py +1 -0
  5. reflex/__init__.pyi +1 -0
  6. reflex/app.py +101 -29
  7. reflex/compiler/compiler.py +11 -62
  8. reflex/compiler/templates.py +12 -3
  9. reflex/compiler/utils.py +20 -4
  10. reflex/components/component.py +366 -88
  11. reflex/components/datadisplay/code.py +1 -1
  12. reflex/components/datadisplay/shiki_code_block.py +97 -86
  13. reflex/components/datadisplay/shiki_code_block.pyi +4 -2
  14. reflex/components/el/elements/forms.py +1 -1
  15. reflex/components/lucide/icon.py +2 -1
  16. reflex/components/lucide/icon.pyi +1 -0
  17. reflex/components/plotly/plotly.py +2 -2
  18. reflex/components/plotly/plotly.pyi +2 -3
  19. reflex/components/radix/primitives/accordion.py +1 -1
  20. reflex/components/radix/primitives/drawer.py +1 -1
  21. reflex/components/radix/primitives/form.py +1 -1
  22. reflex/components/radix/themes/base.py +4 -11
  23. reflex/components/radix/themes/components/icon_button.py +2 -2
  24. reflex/components/radix/themes/components/text_field.py +3 -0
  25. reflex/components/radix/themes/components/text_field.pyi +2 -0
  26. reflex/components/radix/themes/layout/list.py +1 -1
  27. reflex/components/tags/iter_tag.py +3 -5
  28. reflex/config.py +57 -7
  29. reflex/constants/__init__.py +0 -2
  30. reflex/event.py +154 -93
  31. reflex/plugins/__init__.py +7 -0
  32. reflex/plugins/base.py +101 -0
  33. reflex/plugins/tailwind_v3.py +255 -0
  34. reflex/plugins/tailwind_v4.py +258 -0
  35. reflex/state.py +24 -3
  36. reflex/utils/build.py +1 -1
  37. reflex/utils/console.py +1 -1
  38. reflex/utils/exec.py +23 -0
  39. reflex/utils/path_ops.py +26 -6
  40. reflex/utils/prerequisites.py +21 -90
  41. reflex/utils/pyi_generator.py +12 -2
  42. reflex/utils/types.py +15 -1
  43. reflex/vars/base.py +59 -4
  44. reflex/vars/object.py +8 -0
  45. {reflex-0.7.12a1.dist-info → reflex-0.7.13a2.dist-info}/METADATA +2 -2
  46. {reflex-0.7.12a1.dist-info → reflex-0.7.13a2.dist-info}/RECORD +50 -49
  47. scripts/hatch_build.py +17 -0
  48. reflex/.templates/jinja/web/tailwind.config.js.jinja2 +0 -66
  49. reflex/.templates/web/styles/tailwind.css +0 -6
  50. reflex/constants/style.py +0 -16
  51. {reflex-0.7.12a1.dist-info → reflex-0.7.13a2.dist-info}/WHEEL +0 -0
  52. {reflex-0.7.12a1.dist-info → reflex-0.7.13a2.dist-info}/entry_points.txt +0 -0
  53. {reflex-0.7.12a1.dist-info → reflex-0.7.13a2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,258 @@
1
+ """Base class for all plugins."""
2
+
3
+ from pathlib import Path
4
+ from types import SimpleNamespace
5
+
6
+ from reflex.constants.base import Dirs
7
+ from reflex.constants.compiler import Ext, PageNames
8
+ from reflex.plugins.base import Plugin as PluginBase
9
+ from reflex.utils.decorator import once
10
+
11
+
12
+ class Constants(SimpleNamespace):
13
+ """Tailwind constants."""
14
+
15
+ # The Tailwindcss version
16
+ VERSION = "tailwindcss@4.1.7"
17
+ # The Tailwind config.
18
+ CONFIG = "tailwind.config.js"
19
+ # Default Tailwind content paths
20
+ CONTENT = ["./pages/**/*.{js,ts,jsx,tsx}", "./utils/**/*.{js,ts,jsx,tsx}"]
21
+ # Relative tailwind style path to root stylesheet in Dirs.STYLES.
22
+ ROOT_STYLE_PATH = "./tailwind.css"
23
+
24
+ # Content of the style content.
25
+ ROOT_STYLE_CONTENT = """@layer theme, base, components, utilities;
26
+ @config "../tailwind.config.js";
27
+ @import "tailwindcss/theme.css" layer(theme);
28
+ @import "tailwindcss/preflight.css" layer(base);
29
+ @import "{radix_url}" layer(components);
30
+ @import "tailwindcss/utilities.css" layer(utilities);
31
+ """
32
+
33
+ # The default tailwind css.
34
+ TAILWIND_CSS = "@import url('./tailwind.css');"
35
+
36
+
37
+ @once
38
+ def tailwind_config_js_template():
39
+ """Get the Tailwind config template.
40
+
41
+ Returns:
42
+ The Tailwind config template.
43
+ """
44
+ from reflex.compiler.templates import from_string
45
+
46
+ source = r"""
47
+ {# Helper macro to render JS objects and arrays #}
48
+ {% macro render_js(val, indent=2, level=0) -%}
49
+ {%- set space = ' ' * (indent * level) -%}
50
+ {%- set next_space = ' ' * (indent * (level + 1)) -%}
51
+
52
+ {%- if val is mapping -%}
53
+ {
54
+ {%- for k, v in val.items() %}
55
+ {{ next_space }}{{ k if k is string and k.isidentifier() else k|tojson }}: {{ render_js(v, indent, level + 1) }}{{ "," if not loop.last }}
56
+ {%- endfor %}
57
+ {{ space }}}
58
+ {%- elif val is iterable and val is not string -%}
59
+ [
60
+ {%- for item in val %}
61
+ {{ next_space }}{{ render_js(item, indent, level + 1) }}{{ "," if not loop.last }}
62
+ {%- endfor %}
63
+ {{ space }}]
64
+ {%- else -%}
65
+ {{ val | tojson }}
66
+ {%- endif -%}
67
+ {%- endmacro %}
68
+
69
+ {# Extract destructured imports from plugin dicts only #}
70
+ {%- set imports = [] %}
71
+ {%- for plugin in plugins if plugin is mapping and plugin.import is defined %}
72
+ {%- set _ = imports.append(plugin.import) %}
73
+ {%- endfor %}
74
+
75
+ /** @type {import('tailwindcss').Config} */
76
+ {%- for imp in imports %}
77
+ const { {{ imp.name }} } = require({{ imp.from | tojson }});
78
+ {%- endfor %}
79
+
80
+ module.exports = {
81
+ content: {{ render_js(content) }},
82
+ theme: {{ render_js(theme) }},
83
+ {% if darkMode is defined %}darkMode: {{ darkMode | tojson }},{% endif %}
84
+ {% if corePlugins is defined %}corePlugins: {{ render_js(corePlugins) }},{% endif %}
85
+ {% if important is defined %}important: {{ important | tojson }},{% endif %}
86
+ {% if prefix is defined %}prefix: {{ prefix | tojson }},{% endif %}
87
+ {% if separator is defined %}separator: {{ separator | tojson }},{% endif %}
88
+ {% if presets is defined %}
89
+ presets: [
90
+ {% for preset in presets %}
91
+ require({{ preset | tojson }}){{ "," if not loop.last }}
92
+ {% endfor %}
93
+ ],
94
+ {% endif %}
95
+ plugins: [
96
+ {% for plugin in plugins %}
97
+ {% if plugin is mapping %}
98
+ {% if plugin.call is defined %}
99
+ {{ plugin.call }}(
100
+ {%- if plugin.args is defined -%}
101
+ {{ render_js(plugin.args) }}
102
+ {%- endif -%}
103
+ ){{ "," if not loop.last }}
104
+ {% else %}
105
+ require({{ plugin.name | tojson }}){{ "," if not loop.last }}
106
+ {% endif %}
107
+ {% else %}
108
+ require({{ plugin | tojson }}){{ "," if not loop.last }}
109
+ {% endif %}
110
+ {% endfor %}
111
+ ]
112
+ };
113
+ """
114
+
115
+ return from_string(source)
116
+
117
+
118
+ def compile_config(
119
+ config: dict,
120
+ ):
121
+ """Compile the Tailwind config.
122
+
123
+ Args:
124
+ config: The Tailwind config.
125
+
126
+ Returns:
127
+ The compiled Tailwind config.
128
+ """
129
+ return Constants.CONFIG, tailwind_config_js_template().render(
130
+ **config,
131
+ )
132
+
133
+
134
+ def compile_root_style():
135
+ """Compile the Tailwind root style.
136
+
137
+ Returns:
138
+ The compiled Tailwind root style.
139
+ """
140
+ from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET
141
+
142
+ return str(
143
+ Path(Dirs.STYLES) / Constants.ROOT_STYLE_PATH
144
+ ), Constants.ROOT_STYLE_CONTENT.format(
145
+ radix_url=RADIX_THEMES_STYLESHEET,
146
+ )
147
+
148
+
149
+ def _index_of_element_that_has(haystack: list[str], needle: str) -> int | None:
150
+ return next(
151
+ (i for i, line in enumerate(haystack) if needle in line),
152
+ None,
153
+ )
154
+
155
+
156
+ def add_tailwind_to_postcss_config(postcss_file_content: str) -> str:
157
+ """Add tailwind to the postcss config.
158
+
159
+ Args:
160
+ postcss_file_content: The content of the postcss config file.
161
+
162
+ Returns:
163
+ The modified postcss config file content.
164
+ """
165
+ from reflex.constants import Dirs
166
+
167
+ postcss_file_lines = postcss_file_content.splitlines()
168
+
169
+ line_with_postcss_plugins = _index_of_element_that_has(
170
+ postcss_file_lines, "plugins"
171
+ )
172
+ if not line_with_postcss_plugins:
173
+ print( # noqa: T201
174
+ f"Could not find line with 'plugins' in {Dirs.POSTCSS_JS}. "
175
+ "Please make sure the file exists and is valid."
176
+ )
177
+ return postcss_file_content
178
+
179
+ plugins_to_remove = ['"postcss-import"', "tailwindcss", "autoprefixer"]
180
+ plugins_to_add = ['"@tailwindcss/postcss"']
181
+
182
+ for plugin in plugins_to_remove:
183
+ plugin_index = _index_of_element_that_has(postcss_file_lines, plugin)
184
+ if plugin_index is not None:
185
+ postcss_file_lines.pop(plugin_index)
186
+
187
+ for plugin in plugins_to_add[::-1]:
188
+ if not _index_of_element_that_has(postcss_file_lines, plugin):
189
+ postcss_file_lines.insert(
190
+ line_with_postcss_plugins + 1, f" {plugin}: {{}},"
191
+ )
192
+
193
+ return "\n".join(postcss_file_lines)
194
+
195
+
196
+ def add_tailwind_to_css_file(css_file_content: str) -> str:
197
+ """Add tailwind to the css file.
198
+
199
+ Args:
200
+ css_file_content: The content of the css file.
201
+
202
+ Returns:
203
+ The modified css file content.
204
+ """
205
+ from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET
206
+
207
+ if Constants.TAILWIND_CSS.splitlines()[0] in css_file_content:
208
+ return css_file_content
209
+ if RADIX_THEMES_STYLESHEET not in css_file_content:
210
+ print( # noqa: T201
211
+ f"Could not find line with '{RADIX_THEMES_STYLESHEET}' in {Dirs.STYLES}. "
212
+ "Please make sure the file exists and is valid."
213
+ )
214
+ return css_file_content
215
+ return css_file_content.replace(
216
+ f"@import url('{RADIX_THEMES_STYLESHEET}');",
217
+ Constants.TAILWIND_CSS,
218
+ )
219
+
220
+
221
+ class Plugin(PluginBase):
222
+ """Plugin for Tailwind CSS."""
223
+
224
+ def get_frontend_development_dependencies(self, **context) -> list[str]:
225
+ """Get the packages required by the plugin.
226
+
227
+ Args:
228
+ **context: The context for the plugin.
229
+
230
+ Returns:
231
+ A list of packages required by the plugin.
232
+ """
233
+ from reflex.config import get_config
234
+
235
+ config = get_config()
236
+ return [
237
+ plugin if isinstance(plugin, str) else plugin.get("name")
238
+ for plugin in (config.tailwind or {}).get("plugins", [])
239
+ ] + [Constants.VERSION, "@tailwindcss/postcss@4.1.7"]
240
+
241
+ def pre_compile(self, **context):
242
+ """Pre-compile the plugin.
243
+
244
+ Args:
245
+ context: The context for the plugin.
246
+ """
247
+ from reflex.config import get_config
248
+
249
+ config = get_config().tailwind or {}
250
+
251
+ config["content"] = config.get("content", Constants.CONTENT)
252
+ context["add_save_task"](compile_config, config)
253
+ context["add_save_task"](compile_root_style)
254
+ context["add_modify_task"](Dirs.POSTCSS_JS, add_tailwind_to_postcss_config)
255
+ context["add_modify_task"](
256
+ str(Path(Dirs.STYLES) / (PageNames.STYLESHEET_ROOT + Ext.CSS)),
257
+ add_tailwind_to_css_file,
258
+ )
reflex/state.py CHANGED
@@ -1003,6 +1003,18 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1003
1003
  raise ValueError(f"Invalid path: {path}")
1004
1004
  return getattr(substate, name)
1005
1005
 
1006
+ @classmethod
1007
+ def is_user_defined(cls) -> bool:
1008
+ """Check if the state is user-defined.
1009
+
1010
+ Returns:
1011
+ True if the state is user-defined, False otherwise.
1012
+ """
1013
+ return (
1014
+ not cls.__module__.startswith("reflex.")
1015
+ or cls.__module__ == "reflex.istate.dynamic"
1016
+ )
1017
+
1006
1018
  @classmethod
1007
1019
  def _init_var(cls, prop: Var):
1008
1020
  """Initialize a variable.
@@ -1024,7 +1036,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1024
1036
  f'Found var "{prop._js_expr}" with type {prop._var_type}.'
1025
1037
  )
1026
1038
  cls._set_var(prop)
1027
- if get_config().state_auto_setters:
1039
+ if cls.is_user_defined() and get_config().state_auto_setters:
1028
1040
  cls._create_setter(prop)
1029
1041
  cls._set_default_value(prop)
1030
1042
 
@@ -2330,6 +2342,15 @@ class State(BaseState):
2330
2342
  # The hydrated bool.
2331
2343
  is_hydrated: bool = False
2332
2344
 
2345
+ @event
2346
+ def set_is_hydrated(self, value: bool) -> None:
2347
+ """Set the hydrated state.
2348
+
2349
+ Args:
2350
+ value: The hydrated state.
2351
+ """
2352
+ self.is_hydrated = value
2353
+
2333
2354
 
2334
2355
  T = TypeVar("T", bound=BaseState)
2335
2356
 
@@ -2424,7 +2445,7 @@ class OnLoadInternalState(State):
2424
2445
  This is a separate substate to avoid deserializing the entire state tree for every page navigation.
2425
2446
  """
2426
2447
 
2427
- def on_load_internal(self) -> list[Event | EventSpec] | None:
2448
+ def on_load_internal(self) -> list[Event | EventSpec | event.EventCallback] | None:
2428
2449
  """Queue on_load handlers for the current page.
2429
2450
 
2430
2451
  Returns:
@@ -2444,7 +2465,7 @@ class OnLoadInternalState(State):
2444
2465
  self.router.session.client_token,
2445
2466
  router_data=self.router_data,
2446
2467
  ),
2447
- State.set_is_hydrated(True), # pyright: ignore [reportAttributeAccessIssue]
2468
+ State.set_is_hydrated(True),
2448
2469
  ]
2449
2470
 
2450
2471
 
reflex/utils/build.py CHANGED
@@ -230,7 +230,7 @@ def setup_frontend(
230
230
  """
231
231
  # Create the assets dir if it doesn't exist.
232
232
  path_ops.mkdir(constants.Dirs.APP_ASSETS)
233
- path_ops.cp(
233
+ path_ops.copy_tree(
234
234
  src=str(root / constants.Dirs.APP_ASSETS),
235
235
  dest=str(root / prerequisites.get_web_dir() / constants.Dirs.PUBLIC),
236
236
  ignore=tuple(f"*.{ext}" for ext in constants.Reflex.STYLESHEETS_SUPPORTED),
reflex/utils/console.py CHANGED
@@ -65,7 +65,7 @@ def set_log_level(log_level: LogLevel | None):
65
65
  global _LOG_LEVEL
66
66
  if log_level != _LOG_LEVEL:
67
67
  # Set the loglevel persistenly for subprocesses.
68
- os.environ["LOGLEVEL"] = log_level.value
68
+ os.environ["REFLEX_LOGLEVEL"] = log_level.value
69
69
  _LOG_LEVEL = log_level
70
70
 
71
71
 
reflex/utils/exec.py CHANGED
@@ -374,6 +374,23 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
374
374
  )
375
375
 
376
376
 
377
+ HOTRELOAD_IGNORE_EXTENSIONS = (
378
+ "txt",
379
+ "toml",
380
+ "sqlite",
381
+ "yaml",
382
+ "yml",
383
+ "json",
384
+ "sh",
385
+ "bash",
386
+ )
387
+
388
+ HOTRELOAD_IGNORE_PATTERNS = (
389
+ *[rf"^.*\.{ext}$" for ext in HOTRELOAD_IGNORE_EXTENSIONS],
390
+ r"^[^\.]*$", # Ignore files without an extension
391
+ )
392
+
393
+
377
394
  def run_granian_backend(host: str, port: int, loglevel: LogLevel):
378
395
  """Run the backend in development mode using Granian.
379
396
 
@@ -384,6 +401,11 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
384
401
  """
385
402
  console.debug("Using Granian for backend")
386
403
 
404
+ if environment.REFLEX_STRICT_HOT_RELOAD.get():
405
+ import multiprocessing
406
+
407
+ multiprocessing.set_start_method("spawn", force=True)
408
+
387
409
  from granian.constants import Interfaces
388
410
  from granian.log import LogLevels
389
411
  from granian.server import MPServer as Granian
@@ -398,6 +420,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
398
420
  reload=True,
399
421
  reload_paths=get_reload_paths(),
400
422
  reload_ignore_worker_failure=True,
423
+ reload_ignore_patterns=HOTRELOAD_IGNORE_PATTERNS,
401
424
  reload_tick=100,
402
425
  workers_kill_timeout=2,
403
426
  ).serve()
reflex/utils/path_ops.py CHANGED
@@ -42,6 +42,31 @@ def rm(path: str | Path):
42
42
  path.unlink()
43
43
 
44
44
 
45
+ def copy_tree(
46
+ src: str | Path,
47
+ dest: str | Path,
48
+ ignore: tuple[str, ...] | None = None,
49
+ ):
50
+ """Copy a directory tree.
51
+
52
+ Args:
53
+ src: The path to the source directory.
54
+ dest: The path to the destination directory.
55
+ ignore: Ignoring files and directories that match one of the glob-style patterns provided
56
+ """
57
+ src = Path(src)
58
+ dest = Path(dest)
59
+ if dest.exists():
60
+ for item in dest.iterdir():
61
+ rm(item)
62
+ shutil.copytree(
63
+ src,
64
+ dest,
65
+ ignore=shutil.ignore_patterns(*ignore) if ignore is not None else ignore,
66
+ dirs_exist_ok=True,
67
+ )
68
+
69
+
45
70
  def cp(
46
71
  src: str | Path,
47
72
  dest: str | Path,
@@ -65,12 +90,7 @@ def cp(
65
90
  if not overwrite and dest.exists():
66
91
  return False
67
92
  if src.is_dir():
68
- rm(dest)
69
- shutil.copytree(
70
- src,
71
- dest,
72
- ignore=shutil.ignore_patterns(*ignore) if ignore is not None else ignore,
73
- )
93
+ copy_tree(src, dest, ignore)
74
94
  else:
75
95
  shutil.copyfile(src, dest)
76
96
  return True
@@ -17,7 +17,6 @@ import re
17
17
  import shutil
18
18
  import sys
19
19
  import tempfile
20
- import time
21
20
  import typing
22
21
  import zipfile
23
22
  from collections.abc import Callable, Sequence
@@ -40,10 +39,7 @@ from reflex.compiler import templates
40
39
  from reflex.config import Config, environment, get_config
41
40
  from reflex.utils import console, net, path_ops, processes, redir
42
41
  from reflex.utils.decorator import once
43
- from reflex.utils.exceptions import (
44
- GeneratedCodeHasNoFunctionDefsError,
45
- SystemPackageMissingError,
46
- )
42
+ from reflex.utils.exceptions import SystemPackageMissingError
47
43
  from reflex.utils.format import format_library_name
48
44
  from reflex.utils.registry import get_npm_registry
49
45
 
@@ -980,7 +976,7 @@ def initialize_web_directory():
980
976
  project_hash = get_project_hash()
981
977
 
982
978
  console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
983
- path_ops.cp(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
979
+ path_ops.copy_tree(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
984
980
 
985
981
  console.debug("Initializing the web directory.")
986
982
  initialize_package_json()
@@ -1120,7 +1116,7 @@ def _update_next_config(
1120
1116
 
1121
1117
  if transpile_packages:
1122
1118
  next_config["transpilePackages"] = list(
1123
- {format_library_name(p) for p in transpile_packages}
1119
+ dict.fromkeys([format_library_name(p) for p in transpile_packages])
1124
1120
  )
1125
1121
  if export:
1126
1122
  next_config["output"] = "export"
@@ -1317,47 +1313,42 @@ def install_frontend_packages(packages: set[str], config: Config):
1317
1313
  primary_package_manager = install_package_managers[0]
1318
1314
  fallbacks = install_package_managers[1:]
1319
1315
 
1320
- processes.run_process_with_fallbacks(
1321
- [primary_package_manager, "install", "--legacy-peer-deps"],
1316
+ run_package_manager = functools.partial(
1317
+ processes.run_process_with_fallbacks,
1322
1318
  fallbacks=fallbacks,
1323
1319
  analytics_enabled=True,
1324
- show_status_message="Installing base frontend packages",
1325
1320
  cwd=get_web_dir(),
1326
1321
  shell=constants.IS_WINDOWS,
1327
1322
  env=env,
1328
1323
  )
1329
1324
 
1330
- if config.tailwind is not None:
1331
- processes.run_process_with_fallbacks(
1325
+ run_package_manager(
1326
+ [primary_package_manager, "install", "--legacy-peer-deps"],
1327
+ show_status_message="Installing base frontend packages",
1328
+ )
1329
+
1330
+ development_deps: set[str] = set()
1331
+ for plugin in config.plugins:
1332
+ development_deps.update(plugin.get_frontend_development_dependencies())
1333
+ packages.update(plugin.get_frontend_dependencies())
1334
+
1335
+ if development_deps:
1336
+ run_package_manager(
1332
1337
  [
1333
1338
  primary_package_manager,
1334
1339
  "add",
1335
1340
  "--legacy-peer-deps",
1336
1341
  "-d",
1337
- constants.Tailwind.VERSION,
1338
- *[
1339
- plugin if isinstance(plugin, str) else plugin.get("name")
1340
- for plugin in (config.tailwind or {}).get("plugins", [])
1341
- ],
1342
+ *development_deps,
1342
1343
  ],
1343
- fallbacks=fallbacks,
1344
- analytics_enabled=True,
1345
- show_status_message="Installing tailwind",
1346
- cwd=get_web_dir(),
1347
- shell=constants.IS_WINDOWS,
1348
- env=env,
1344
+ show_status_message="Installing frontend development dependencies",
1349
1345
  )
1350
1346
 
1351
1347
  # Install custom packages defined in frontend_packages
1352
- if len(packages) > 0:
1353
- processes.run_process_with_fallbacks(
1348
+ if packages:
1349
+ run_package_manager(
1354
1350
  [primary_package_manager, "add", "--legacy-peer-deps", *packages],
1355
- fallbacks=fallbacks,
1356
- analytics_enabled=True,
1357
1351
  show_status_message="Installing frontend packages from config and components",
1358
- cwd=get_web_dir(),
1359
- shell=constants.IS_WINDOWS,
1360
- env=env,
1361
1352
  )
1362
1353
 
1363
1354
 
@@ -1931,66 +1922,6 @@ def get_init_cli_prompt_options() -> list[Template]:
1931
1922
  ]
1932
1923
 
1933
1924
 
1934
- def initialize_main_module_index_from_generation(app_name: str, generation_hash: str):
1935
- """Overwrite the `index` function in the main module with reflex.build generated code.
1936
-
1937
- Args:
1938
- app_name: The name of the app.
1939
- generation_hash: The generation hash from reflex.build.
1940
-
1941
- Raises:
1942
- GeneratedCodeHasNoFunctionDefsError: If the fetched code has no function definitions
1943
- (the refactored reflex code is expected to have at least one root function defined).
1944
- """
1945
- # Download the reflex code for the generation.
1946
- url = constants.Templates.REFLEX_BUILD_CODE_URL.format(
1947
- generation_hash=generation_hash
1948
- )
1949
- resp = net.get(url)
1950
- while resp.status_code == httpx.codes.SERVICE_UNAVAILABLE:
1951
- console.debug("Waiting for the code to be generated...")
1952
- time.sleep(1)
1953
- resp = net.get(url)
1954
- resp.raise_for_status()
1955
-
1956
- # Determine the name of the last function, which renders the generated code.
1957
- defined_funcs = re.findall(r"def ([a-zA-Z_]+)\(", resp.text)
1958
- if not defined_funcs:
1959
- raise GeneratedCodeHasNoFunctionDefsError(
1960
- f"No function definitions found in generated code from {url!r}."
1961
- )
1962
- render_func_name = defined_funcs[-1]
1963
-
1964
- def replace_content(_match: re.Match) -> str:
1965
- return "\n".join(
1966
- [
1967
- resp.text,
1968
- "",
1969
- "def index() -> rx.Component:",
1970
- f" return {render_func_name}()",
1971
- "",
1972
- "",
1973
- ],
1974
- )
1975
-
1976
- main_module_path = Path(app_name, app_name + constants.Ext.PY)
1977
- main_module_code = main_module_path.read_text()
1978
-
1979
- main_module_code = re.sub(
1980
- r"def index\(\).*:\n([^\n]\s+.*\n+)+",
1981
- replace_content,
1982
- main_module_code,
1983
- )
1984
- # Make the app use light mode until flexgen enforces the conversion of
1985
- # tailwind colors to radix colors.
1986
- main_module_code = re.sub(
1987
- r"app\s*=\s*rx\.App\(\s*\)",
1988
- 'app = rx.App(theme=rx.theme(color_mode="light"))',
1989
- main_module_code,
1990
- )
1991
- main_module_path.write_text(main_module_code)
1992
-
1993
-
1994
1925
  def format_address_width(address_width: str | None) -> int | None:
1995
1926
  """Cast address width to an int.
1996
1927
 
@@ -10,6 +10,7 @@ import json
10
10
  import logging
11
11
  import re
12
12
  import subprocess
13
+ import sys
13
14
  import typing
14
15
  from collections.abc import Callable, Iterable, Sequence
15
16
  from fileinput import FileInput
@@ -387,13 +388,22 @@ def _extract_class_props_as_ast_nodes(
387
388
  if isinstance(default, Var):
388
389
  default = default._decode()
389
390
 
391
+ modules = {cls.__module__ for cls in target_class.__mro__}
392
+ available_vars = {}
393
+ for module in modules:
394
+ available_vars.update(sys.modules[module].__dict__)
395
+
390
396
  kwargs.append(
391
397
  (
392
398
  ast.arg(
393
399
  arg=name,
394
400
  annotation=ast.Name(
395
401
  id=OVERWRITE_TYPES.get(
396
- name, _get_type_hint(value, type_hint_globals)
402
+ name,
403
+ _get_type_hint(
404
+ value,
405
+ type_hint_globals | available_vars,
406
+ ),
397
407
  )
398
408
  ),
399
409
  ),
@@ -1227,7 +1237,7 @@ class PyiGenerator:
1227
1237
  continue
1228
1238
  subprocess.run(["git", "checkout", changed_file])
1229
1239
 
1230
- if cpu_count() == 1 or len(file_targets) < 5:
1240
+ if True:
1231
1241
  self._scan_files(file_targets)
1232
1242
  else:
1233
1243
  self._scan_files_multiprocess(file_targets)
reflex/utils/types.py CHANGED
@@ -254,6 +254,20 @@ def is_optional(cls: GenericType) -> bool:
254
254
  return is_union(cls) and type(None) in get_args(cls)
255
255
 
256
256
 
257
+ def is_classvar(a_type: Any) -> bool:
258
+ """Check if a type is a ClassVar.
259
+
260
+ Args:
261
+ a_type: The type to check.
262
+
263
+ Returns:
264
+ Whether the type is a ClassVar.
265
+ """
266
+ return a_type is ClassVar or (
267
+ type(a_type) is _GenericAlias and a_type.__origin__ is ClassVar
268
+ )
269
+
270
+
257
271
  def true_type_for_pydantic_field(f: ModelField):
258
272
  """Get the type for a pydantic field.
259
273
 
@@ -982,7 +996,7 @@ def typehint_issubclass(
982
996
  Returns:
983
997
  Whether the type hint is a subclass of the other type hint.
984
998
  """
985
- if possible_superclass is Any:
999
+ if possible_subclass is possible_superclass or possible_superclass is Any:
986
1000
  return True
987
1001
  if possible_subclass is Any:
988
1002
  return treat_any_as_subtype_of_everything