reflex 0.7.12__py3-none-any.whl → 0.7.13__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/.templates/jinja/app/rxconfig.py.jinja2 +1 -0
- reflex/.templates/web/postcss.config.js +0 -1
- reflex/.templates/web/utils/state.js +1 -1
- reflex/__init__.py +1 -0
- reflex/__init__.pyi +1 -0
- reflex/app.py +101 -29
- reflex/compiler/compiler.py +11 -62
- reflex/compiler/templates.py +12 -3
- reflex/compiler/utils.py +20 -4
- reflex/components/component.py +366 -88
- reflex/components/core/helmet.pyi +66 -0
- reflex/components/datadisplay/code.py +1 -1
- reflex/components/datadisplay/shiki_code_block.py +97 -86
- reflex/components/datadisplay/shiki_code_block.pyi +4 -2
- reflex/components/el/elements/forms.py +1 -1
- reflex/components/lucide/icon.py +2 -1
- reflex/components/lucide/icon.pyi +1 -0
- reflex/components/plotly/plotly.py +2 -2
- reflex/components/plotly/plotly.pyi +2 -3
- reflex/components/radix/primitives/accordion.py +1 -1
- reflex/components/radix/primitives/drawer.py +1 -1
- reflex/components/radix/primitives/form.py +1 -1
- reflex/components/radix/themes/base.py +4 -11
- reflex/components/radix/themes/components/icon_button.py +2 -2
- reflex/components/radix/themes/components/text_field.py +3 -0
- reflex/components/radix/themes/components/text_field.pyi +2 -0
- reflex/components/radix/themes/layout/list.py +1 -1
- reflex/components/tags/iter_tag.py +3 -5
- reflex/config.py +54 -7
- reflex/constants/__init__.py +0 -2
- reflex/event.py +154 -93
- reflex/experimental/client_state.py +3 -1
- reflex/plugins/__init__.py +7 -0
- reflex/plugins/base.py +101 -0
- reflex/plugins/tailwind_v3.py +255 -0
- reflex/plugins/tailwind_v4.py +258 -0
- reflex/state.py +24 -3
- reflex/utils/build.py +1 -1
- reflex/utils/console.py +1 -1
- reflex/utils/exec.py +18 -0
- reflex/utils/path_ops.py +26 -6
- reflex/utils/prerequisites.py +21 -90
- reflex/utils/pyi_generator.py +12 -2
- reflex/utils/types.py +15 -1
- reflex/vars/base.py +59 -4
- reflex/vars/object.py +8 -0
- {reflex-0.7.12.dist-info → reflex-0.7.13.dist-info}/METADATA +2 -2
- {reflex-0.7.12.dist-info → reflex-0.7.13.dist-info}/RECORD +52 -50
- scripts/hatch_build.py +17 -0
- reflex/.templates/jinja/web/tailwind.config.js.jinja2 +0 -66
- reflex/.templates/web/styles/tailwind.css +0 -6
- reflex/constants/style.py +0 -16
- {reflex-0.7.12.dist-info → reflex-0.7.13.dist-info}/WHEEL +0 -0
- {reflex-0.7.12.dist-info → reflex-0.7.13.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.12.dist-info → reflex-0.7.13.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),
|
|
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.
|
|
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["
|
|
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
|
|
|
@@ -403,6 +420,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
403
420
|
reload=True,
|
|
404
421
|
reload_paths=get_reload_paths(),
|
|
405
422
|
reload_ignore_worker_failure=True,
|
|
423
|
+
reload_ignore_patterns=HOTRELOAD_IGNORE_PATTERNS,
|
|
406
424
|
reload_tick=100,
|
|
407
425
|
workers_kill_timeout=2,
|
|
408
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
|
-
|
|
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
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
1321
|
-
|
|
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
|
-
|
|
1331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1353
|
-
|
|
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
|
|
reflex/utils/pyi_generator.py
CHANGED
|
@@ -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,
|
|
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
|
|
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
|