reflex 0.5.7__py3-none-any.whl → 0.5.8__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/web/utils/state.js +1 -1
- reflex/app.py +20 -2
- reflex/components/core/banner.py +14 -0
- reflex/components/core/banner.pyi +3 -3
- reflex/components/core/debounce.py +3 -0
- reflex/components/el/__init__.py +1 -0
- reflex/components/el/__init__.pyi +8 -5
- reflex/components/el/elements/__init__.py +3 -1
- reflex/components/el/elements/__init__.pyi +8 -6
- reflex/components/el/elements/media.py +98 -18
- reflex/components/el/elements/media.pyi +523 -18
- reflex/components/el/elements/metadata.py +5 -1
- reflex/components/radix/primitives/base.py +1 -1
- reflex/components/radix/themes/layout/list.py +0 -2
- reflex/components/recharts/cartesian.py +46 -20
- reflex/components/recharts/cartesian.pyi +26 -14
- reflex/components/recharts/charts.py +4 -0
- reflex/components/recharts/charts.pyi +3 -0
- reflex/components/recharts/general.py +23 -9
- reflex/components/recharts/general.pyi +6 -4
- reflex/components/recharts/polar.py +35 -11
- reflex/components/recharts/polar.pyi +35 -7
- reflex/components/sonner/toast.py +28 -2
- reflex/components/sonner/toast.pyi +14 -7
- reflex/constants/base.py +21 -0
- reflex/constants/event.py +2 -0
- reflex/experimental/vars/__init__.py +17 -0
- reflex/experimental/vars/base.py +282 -15
- reflex/experimental/vars/function.py +214 -0
- reflex/experimental/vars/number.py +1295 -0
- reflex/experimental/vars/sequence.py +1039 -0
- reflex/reflex.py +29 -2
- reflex/state.py +59 -10
- reflex/utils/imports.py +71 -8
- reflex/utils/prerequisites.py +115 -35
- reflex/utils/pyi_generator.py +2 -0
- reflex/utils/redir.py +52 -0
- reflex/vars.py +220 -11
- reflex/vars.pyi +20 -2
- {reflex-0.5.7.dist-info → reflex-0.5.8.dist-info}/METADATA +2 -2
- {reflex-0.5.7.dist-info → reflex-0.5.8.dist-info}/RECORD +44 -40
- {reflex-0.5.7.dist-info → reflex-0.5.8.dist-info}/LICENSE +0 -0
- {reflex-0.5.7.dist-info → reflex-0.5.8.dist-info}/WHEEL +0 -0
- {reflex-0.5.7.dist-info → reflex-0.5.8.dist-info}/entry_points.txt +0 -0
reflex/reflex.py
CHANGED
|
@@ -16,7 +16,7 @@ from reflex_cli.utils import dependency
|
|
|
16
16
|
from reflex import constants
|
|
17
17
|
from reflex.config import get_config
|
|
18
18
|
from reflex.custom_components.custom_components import custom_components_cli
|
|
19
|
-
from reflex.utils import console, telemetry
|
|
19
|
+
from reflex.utils import console, redir, telemetry
|
|
20
20
|
|
|
21
21
|
# Disable typer+rich integration for help panels
|
|
22
22
|
typer.core.rich = False # type: ignore
|
|
@@ -65,6 +65,7 @@ def _init(
|
|
|
65
65
|
name: str,
|
|
66
66
|
template: str | None = None,
|
|
67
67
|
loglevel: constants.LogLevel = config.loglevel,
|
|
68
|
+
ai: bool = False,
|
|
68
69
|
):
|
|
69
70
|
"""Initialize a new Reflex app in the given directory."""
|
|
70
71
|
from reflex.utils import exec, prerequisites
|
|
@@ -91,9 +92,31 @@ def _init(
|
|
|
91
92
|
# Set up the web project.
|
|
92
93
|
prerequisites.initialize_frontend_dependencies()
|
|
93
94
|
|
|
95
|
+
# Integrate with reflex.build.
|
|
96
|
+
generation_hash = None
|
|
97
|
+
if ai:
|
|
98
|
+
if template is None:
|
|
99
|
+
# If AI is requested and no template specified, redirect the user to reflex.build.
|
|
100
|
+
generation_hash = redir.reflex_build_redirect()
|
|
101
|
+
elif prerequisites.is_generation_hash(template):
|
|
102
|
+
# Otherwise treat the template as a generation hash.
|
|
103
|
+
generation_hash = template
|
|
104
|
+
else:
|
|
105
|
+
console.error(
|
|
106
|
+
"Cannot use `--template` option with `--ai` option. Please remove `--template` option."
|
|
107
|
+
)
|
|
108
|
+
raise typer.Exit(2)
|
|
109
|
+
template = constants.Templates.DEFAULT
|
|
110
|
+
|
|
94
111
|
# Initialize the app.
|
|
95
112
|
prerequisites.initialize_app(app_name, template)
|
|
96
113
|
|
|
114
|
+
# If a reflex.build generation hash is available, download the code and apply it to the main module.
|
|
115
|
+
if generation_hash:
|
|
116
|
+
prerequisites.initialize_main_module_index_from_generation(
|
|
117
|
+
app_name, generation_hash=generation_hash
|
|
118
|
+
)
|
|
119
|
+
|
|
97
120
|
# Migrate Pynecone projects to Reflex.
|
|
98
121
|
prerequisites.migrate_to_reflex()
|
|
99
122
|
|
|
@@ -119,9 +142,13 @@ def init(
|
|
|
119
142
|
loglevel: constants.LogLevel = typer.Option(
|
|
120
143
|
config.loglevel, help="The log level to use."
|
|
121
144
|
),
|
|
145
|
+
ai: bool = typer.Option(
|
|
146
|
+
False,
|
|
147
|
+
help="Use AI to create the initial template. Cannot be used with existing app or `--template` option.",
|
|
148
|
+
),
|
|
122
149
|
):
|
|
123
150
|
"""Initialize a new Reflex app in the current directory."""
|
|
124
|
-
_init(name, template, loglevel)
|
|
151
|
+
_init(name, template, loglevel, ai)
|
|
125
152
|
|
|
126
153
|
|
|
127
154
|
def _run(
|
reflex/state.py
CHANGED
|
@@ -31,6 +31,8 @@ from typing import (
|
|
|
31
31
|
import dill
|
|
32
32
|
from sqlalchemy.orm import DeclarativeBase
|
|
33
33
|
|
|
34
|
+
from reflex.config import get_config
|
|
35
|
+
|
|
34
36
|
try:
|
|
35
37
|
import pydantic.v1 as pydantic
|
|
36
38
|
except ModuleNotFoundError:
|
|
@@ -42,7 +44,6 @@ from redis.exceptions import ResponseError
|
|
|
42
44
|
|
|
43
45
|
from reflex import constants
|
|
44
46
|
from reflex.base import Base
|
|
45
|
-
from reflex.config import get_config
|
|
46
47
|
from reflex.event import (
|
|
47
48
|
BACKGROUND_TASK_MARKER,
|
|
48
49
|
Event,
|
|
@@ -201,7 +202,7 @@ def _no_chain_background_task(
|
|
|
201
202
|
|
|
202
203
|
def _substate_key(
|
|
203
204
|
token: str,
|
|
204
|
-
state_cls_or_name: BaseState | Type[BaseState] | str |
|
|
205
|
+
state_cls_or_name: BaseState | Type[BaseState] | str | Sequence[str],
|
|
205
206
|
) -> str:
|
|
206
207
|
"""Get the substate key.
|
|
207
208
|
|
|
@@ -2028,19 +2029,38 @@ class StateProxy(wrapt.ObjectProxy):
|
|
|
2028
2029
|
self.counter += 1
|
|
2029
2030
|
"""
|
|
2030
2031
|
|
|
2031
|
-
def __init__(
|
|
2032
|
+
def __init__(
|
|
2033
|
+
self, state_instance, parent_state_proxy: Optional["StateProxy"] = None
|
|
2034
|
+
):
|
|
2032
2035
|
"""Create a proxy for a state instance.
|
|
2033
2036
|
|
|
2037
|
+
If `get_state` is used on a StateProxy, the resulting state will be
|
|
2038
|
+
linked to the given state via parent_state_proxy. The first state in the
|
|
2039
|
+
chain is the state that initiated the background task.
|
|
2040
|
+
|
|
2034
2041
|
Args:
|
|
2035
2042
|
state_instance: The state instance to proxy.
|
|
2043
|
+
parent_state_proxy: The parent state proxy, for linked mutability and context tracking.
|
|
2036
2044
|
"""
|
|
2037
2045
|
super().__init__(state_instance)
|
|
2038
2046
|
# compile is not relevant to backend logic
|
|
2039
2047
|
self._self_app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
|
2040
|
-
self._self_substate_path = state_instance.get_full_name().split(".")
|
|
2048
|
+
self._self_substate_path = tuple(state_instance.get_full_name().split("."))
|
|
2041
2049
|
self._self_actx = None
|
|
2042
2050
|
self._self_mutable = False
|
|
2043
2051
|
self._self_actx_lock = asyncio.Lock()
|
|
2052
|
+
self._self_actx_lock_holder = None
|
|
2053
|
+
self._self_parent_state_proxy = parent_state_proxy
|
|
2054
|
+
|
|
2055
|
+
def _is_mutable(self) -> bool:
|
|
2056
|
+
"""Check if the state is mutable.
|
|
2057
|
+
|
|
2058
|
+
Returns:
|
|
2059
|
+
Whether the state is mutable.
|
|
2060
|
+
"""
|
|
2061
|
+
if self._self_parent_state_proxy is not None:
|
|
2062
|
+
return self._self_parent_state_proxy._is_mutable()
|
|
2063
|
+
return self._self_mutable
|
|
2044
2064
|
|
|
2045
2065
|
async def __aenter__(self) -> StateProxy:
|
|
2046
2066
|
"""Enter the async context manager protocol.
|
|
@@ -2053,8 +2073,31 @@ class StateProxy(wrapt.ObjectProxy):
|
|
|
2053
2073
|
|
|
2054
2074
|
Returns:
|
|
2055
2075
|
This StateProxy instance in mutable mode.
|
|
2056
|
-
|
|
2076
|
+
|
|
2077
|
+
Raises:
|
|
2078
|
+
ImmutableStateError: If the state is already mutable.
|
|
2079
|
+
"""
|
|
2080
|
+
if self._self_parent_state_proxy is not None:
|
|
2081
|
+
parent_state = (
|
|
2082
|
+
await self._self_parent_state_proxy.__aenter__()
|
|
2083
|
+
).__wrapped__
|
|
2084
|
+
super().__setattr__(
|
|
2085
|
+
"__wrapped__",
|
|
2086
|
+
await parent_state.get_state(
|
|
2087
|
+
State.get_class_substate(self._self_substate_path)
|
|
2088
|
+
),
|
|
2089
|
+
)
|
|
2090
|
+
return self
|
|
2091
|
+
current_task = asyncio.current_task()
|
|
2092
|
+
if (
|
|
2093
|
+
self._self_actx_lock.locked()
|
|
2094
|
+
and current_task == self._self_actx_lock_holder
|
|
2095
|
+
):
|
|
2096
|
+
raise ImmutableStateError(
|
|
2097
|
+
"The state is already mutable. Do not nest `async with self` blocks."
|
|
2098
|
+
)
|
|
2057
2099
|
await self._self_actx_lock.acquire()
|
|
2100
|
+
self._self_actx_lock_holder = current_task
|
|
2058
2101
|
self._self_actx = self._self_app.modify_state(
|
|
2059
2102
|
token=_substate_key(
|
|
2060
2103
|
self.__wrapped__.router.session.client_token,
|
|
@@ -2076,12 +2119,16 @@ class StateProxy(wrapt.ObjectProxy):
|
|
|
2076
2119
|
Args:
|
|
2077
2120
|
exc_info: The exception info tuple.
|
|
2078
2121
|
"""
|
|
2122
|
+
if self._self_parent_state_proxy is not None:
|
|
2123
|
+
await self._self_parent_state_proxy.__aexit__(*exc_info)
|
|
2124
|
+
return
|
|
2079
2125
|
if self._self_actx is None:
|
|
2080
2126
|
return
|
|
2081
2127
|
self._self_mutable = False
|
|
2082
2128
|
try:
|
|
2083
2129
|
await self._self_actx.__aexit__(*exc_info)
|
|
2084
2130
|
finally:
|
|
2131
|
+
self._self_actx_lock_holder = None
|
|
2085
2132
|
self._self_actx_lock.release()
|
|
2086
2133
|
self._self_actx = None
|
|
2087
2134
|
|
|
@@ -2116,7 +2163,7 @@ class StateProxy(wrapt.ObjectProxy):
|
|
|
2116
2163
|
Raises:
|
|
2117
2164
|
ImmutableStateError: If the state is not in mutable mode.
|
|
2118
2165
|
"""
|
|
2119
|
-
if name in ["substates", "parent_state"] and not self.
|
|
2166
|
+
if name in ["substates", "parent_state"] and not self._is_mutable():
|
|
2120
2167
|
raise ImmutableStateError(
|
|
2121
2168
|
"Background task StateProxy is immutable outside of a context "
|
|
2122
2169
|
"manager. Use `async with self` to modify state."
|
|
@@ -2156,7 +2203,7 @@ class StateProxy(wrapt.ObjectProxy):
|
|
|
2156
2203
|
"""
|
|
2157
2204
|
if (
|
|
2158
2205
|
name.startswith("_self_") # wrapper attribute
|
|
2159
|
-
or self.
|
|
2206
|
+
or self._is_mutable() # lock held
|
|
2160
2207
|
# non-persisted state attribute
|
|
2161
2208
|
or name in self.__wrapped__.get_skip_vars()
|
|
2162
2209
|
):
|
|
@@ -2180,7 +2227,7 @@ class StateProxy(wrapt.ObjectProxy):
|
|
|
2180
2227
|
Raises:
|
|
2181
2228
|
ImmutableStateError: If the state is not in mutable mode.
|
|
2182
2229
|
"""
|
|
2183
|
-
if not self.
|
|
2230
|
+
if not self._is_mutable():
|
|
2184
2231
|
raise ImmutableStateError(
|
|
2185
2232
|
"Background task StateProxy is immutable outside of a context "
|
|
2186
2233
|
"manager. Use `async with self` to modify state."
|
|
@@ -2199,12 +2246,14 @@ class StateProxy(wrapt.ObjectProxy):
|
|
|
2199
2246
|
Raises:
|
|
2200
2247
|
ImmutableStateError: If the state is not in mutable mode.
|
|
2201
2248
|
"""
|
|
2202
|
-
if not self.
|
|
2249
|
+
if not self._is_mutable():
|
|
2203
2250
|
raise ImmutableStateError(
|
|
2204
2251
|
"Background task StateProxy is immutable outside of a context "
|
|
2205
2252
|
"manager. Use `async with self` to modify state."
|
|
2206
2253
|
)
|
|
2207
|
-
return
|
|
2254
|
+
return type(self)(
|
|
2255
|
+
await self.__wrapped__.get_state(state_cls), parent_state_proxy=self
|
|
2256
|
+
)
|
|
2208
2257
|
|
|
2209
2258
|
def _as_state_update(self, *args, **kwargs) -> StateUpdate:
|
|
2210
2259
|
"""Temporarily allow mutability to access parent_state.
|
reflex/utils/imports.py
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections import defaultdict
|
|
6
|
-
from typing import Dict, List, Optional, Union
|
|
6
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
7
7
|
|
|
8
8
|
from reflex.base import Base
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def merge_imports(
|
|
11
|
+
def merge_imports(
|
|
12
|
+
*imports: ImportDict | ParsedImportDict | ImmutableParsedImportDict,
|
|
13
|
+
) -> ParsedImportDict:
|
|
12
14
|
"""Merge multiple import dicts together.
|
|
13
15
|
|
|
14
16
|
Args:
|
|
@@ -19,7 +21,9 @@ def merge_imports(*imports: ImportDict | ParsedImportDict) -> ParsedImportDict:
|
|
|
19
21
|
"""
|
|
20
22
|
all_imports = defaultdict(list)
|
|
21
23
|
for import_dict in imports:
|
|
22
|
-
for lib, fields in
|
|
24
|
+
for lib, fields in (
|
|
25
|
+
import_dict if isinstance(import_dict, tuple) else import_dict.items()
|
|
26
|
+
):
|
|
23
27
|
all_imports[lib].extend(fields)
|
|
24
28
|
return all_imports
|
|
25
29
|
|
|
@@ -48,7 +52,9 @@ def parse_imports(imports: ImportDict | ParsedImportDict) -> ParsedImportDict:
|
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
|
|
51
|
-
def collapse_imports(
|
|
55
|
+
def collapse_imports(
|
|
56
|
+
imports: ParsedImportDict | ImmutableParsedImportDict,
|
|
57
|
+
) -> ParsedImportDict:
|
|
52
58
|
"""Remove all duplicate ImportVar within an ImportDict.
|
|
53
59
|
|
|
54
60
|
Args:
|
|
@@ -58,8 +64,14 @@ def collapse_imports(imports: ParsedImportDict) -> ParsedImportDict:
|
|
|
58
64
|
The collapsed import dict.
|
|
59
65
|
"""
|
|
60
66
|
return {
|
|
61
|
-
lib:
|
|
62
|
-
|
|
67
|
+
lib: (
|
|
68
|
+
list(set(import_vars))
|
|
69
|
+
if isinstance(import_vars, list)
|
|
70
|
+
else list(import_vars)
|
|
71
|
+
)
|
|
72
|
+
for lib, import_vars in (
|
|
73
|
+
imports if isinstance(imports, tuple) else imports.items()
|
|
74
|
+
)
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
|
|
@@ -99,11 +111,61 @@ class ImportVar(Base):
|
|
|
99
111
|
else:
|
|
100
112
|
return self.tag or ""
|
|
101
113
|
|
|
114
|
+
def __lt__(self, other: ImportVar) -> bool:
|
|
115
|
+
"""Compare two ImportVar objects.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
other: The other ImportVar object to compare.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Whether this ImportVar object is less than the other.
|
|
122
|
+
"""
|
|
123
|
+
return (
|
|
124
|
+
self.tag,
|
|
125
|
+
self.is_default,
|
|
126
|
+
self.alias,
|
|
127
|
+
self.install,
|
|
128
|
+
self.render,
|
|
129
|
+
self.transpile,
|
|
130
|
+
) < (
|
|
131
|
+
other.tag,
|
|
132
|
+
other.is_default,
|
|
133
|
+
other.alias,
|
|
134
|
+
other.install,
|
|
135
|
+
other.render,
|
|
136
|
+
other.transpile,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def __eq__(self, other: ImportVar) -> bool:
|
|
140
|
+
"""Check if two ImportVar objects are equal.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
other: The other ImportVar object to compare.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Whether the two ImportVar objects are equal.
|
|
147
|
+
"""
|
|
148
|
+
return (
|
|
149
|
+
self.tag,
|
|
150
|
+
self.is_default,
|
|
151
|
+
self.alias,
|
|
152
|
+
self.install,
|
|
153
|
+
self.render,
|
|
154
|
+
self.transpile,
|
|
155
|
+
) == (
|
|
156
|
+
other.tag,
|
|
157
|
+
other.is_default,
|
|
158
|
+
other.alias,
|
|
159
|
+
other.install,
|
|
160
|
+
other.render,
|
|
161
|
+
other.transpile,
|
|
162
|
+
)
|
|
163
|
+
|
|
102
164
|
def __hash__(self) -> int:
|
|
103
|
-
"""
|
|
165
|
+
"""Hash the ImportVar object.
|
|
104
166
|
|
|
105
167
|
Returns:
|
|
106
|
-
The hash of the
|
|
168
|
+
The hash of the ImportVar object.
|
|
107
169
|
"""
|
|
108
170
|
return hash(
|
|
109
171
|
(
|
|
@@ -120,3 +182,4 @@ class ImportVar(Base):
|
|
|
120
182
|
ImportTypes = Union[str, ImportVar, List[Union[str, ImportVar]], List[ImportVar]]
|
|
121
183
|
ImportDict = Dict[str, ImportTypes]
|
|
122
184
|
ParsedImportDict = Dict[str, List[ImportVar]]
|
|
185
|
+
ImmutableParsedImportDict = Tuple[Tuple[str, Tuple[ImportVar, ...]], ...]
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -16,6 +16,7 @@ import shutil
|
|
|
16
16
|
import stat
|
|
17
17
|
import sys
|
|
18
18
|
import tempfile
|
|
19
|
+
import textwrap
|
|
19
20
|
import zipfile
|
|
20
21
|
from datetime import datetime
|
|
21
22
|
from fileinput import FileInput
|
|
@@ -1310,39 +1311,63 @@ def migrate_to_reflex():
|
|
|
1310
1311
|
print(line, end="")
|
|
1311
1312
|
|
|
1312
1313
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1314
|
+
RELEASES_URL = f"https://api.github.com/repos/reflex-dev/templates/releases"
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
def fetch_app_templates(version: str) -> dict[str, Template]:
|
|
1318
|
+
"""Fetch a dict of templates from the templates repo using github API.
|
|
1319
|
+
|
|
1320
|
+
Args:
|
|
1321
|
+
version: The version of the templates to fetch.
|
|
1315
1322
|
|
|
1316
1323
|
Returns:
|
|
1317
|
-
The
|
|
1324
|
+
The dict of templates.
|
|
1318
1325
|
"""
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
"Skip fetching App templates. No backend URL is specified in the config."
|
|
1323
|
-
)
|
|
1324
|
-
return {}
|
|
1325
|
-
try:
|
|
1326
|
-
response = httpx.get(
|
|
1327
|
-
f"{config.cp_backend_url}{constants.Templates.APP_TEMPLATES_ROUTE}"
|
|
1328
|
-
)
|
|
1326
|
+
|
|
1327
|
+
def get_release_by_tag(tag: str) -> dict | None:
|
|
1328
|
+
response = httpx.get(RELEASES_URL)
|
|
1329
1329
|
response.raise_for_status()
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1330
|
+
releases = response.json()
|
|
1331
|
+
for release in releases:
|
|
1332
|
+
if release["tag_name"] == f"v{tag}":
|
|
1333
|
+
return release
|
|
1334
|
+
return None
|
|
1335
|
+
|
|
1336
|
+
release = get_release_by_tag(version)
|
|
1337
|
+
if release is None:
|
|
1338
|
+
console.warn(f"No templates known for version {version}")
|
|
1336
1339
|
return {}
|
|
1337
|
-
|
|
1338
|
-
|
|
1340
|
+
|
|
1341
|
+
assets = release.get("assets", [])
|
|
1342
|
+
asset = next((a for a in assets if a["name"] == "templates.json"), None)
|
|
1343
|
+
if asset is None:
|
|
1344
|
+
console.warn(f"Templates metadata not found for version {version}")
|
|
1339
1345
|
return {}
|
|
1346
|
+
else:
|
|
1347
|
+
templates_url = asset["browser_download_url"]
|
|
1340
1348
|
|
|
1349
|
+
templates_data = httpx.get(templates_url, follow_redirects=True).json()["templates"]
|
|
1341
1350
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1351
|
+
for template in templates_data:
|
|
1352
|
+
if template["name"] == "blank":
|
|
1353
|
+
template["code_url"] = ""
|
|
1354
|
+
continue
|
|
1355
|
+
template["code_url"] = next(
|
|
1356
|
+
(
|
|
1357
|
+
a["browser_download_url"]
|
|
1358
|
+
for a in assets
|
|
1359
|
+
if a["name"] == f"{template['name']}.zip"
|
|
1360
|
+
),
|
|
1361
|
+
None,
|
|
1362
|
+
)
|
|
1363
|
+
return {
|
|
1364
|
+
tp["name"]: Template.parse_obj(tp)
|
|
1365
|
+
for tp in templates_data
|
|
1366
|
+
if not tp["hidden"] and tp["code_url"] is not None
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
|
|
1370
|
+
def create_config_init_app_from_remote_template(app_name: str, template_url: str):
|
|
1346
1371
|
"""Create new rxconfig and initialize app using a remote template.
|
|
1347
1372
|
|
|
1348
1373
|
Args:
|
|
@@ -1436,15 +1461,20 @@ def initialize_app(app_name: str, template: str | None = None):
|
|
|
1436
1461
|
telemetry.send("reinit")
|
|
1437
1462
|
return
|
|
1438
1463
|
|
|
1439
|
-
|
|
1440
|
-
templates: dict[str, Template] = fetch_app_templates()
|
|
1464
|
+
templates: dict[str, Template] = {}
|
|
1441
1465
|
|
|
1442
|
-
#
|
|
1443
|
-
if template is None
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1466
|
+
# Don't fetch app templates if the user directly asked for DEFAULT.
|
|
1467
|
+
if template is None or (template != constants.Templates.DEFAULT):
|
|
1468
|
+
try:
|
|
1469
|
+
# Get the available templates
|
|
1470
|
+
templates = fetch_app_templates(constants.Reflex.VERSION)
|
|
1471
|
+
if template is None and len(templates) > 0:
|
|
1472
|
+
template = prompt_for_template(list(templates.values()))
|
|
1473
|
+
except Exception as e:
|
|
1474
|
+
console.warn("Failed to fetch templates. Falling back to default template.")
|
|
1475
|
+
console.debug(f"Error while fetching templates: {e}")
|
|
1476
|
+
finally:
|
|
1477
|
+
template = template or constants.Templates.DEFAULT
|
|
1448
1478
|
|
|
1449
1479
|
# If the blank template is selected, create a blank app.
|
|
1450
1480
|
if template == constants.Templates.DEFAULT:
|
|
@@ -1467,14 +1497,52 @@ def initialize_app(app_name: str, template: str | None = None):
|
|
|
1467
1497
|
else:
|
|
1468
1498
|
console.error(f"Template `{template}` not found.")
|
|
1469
1499
|
raise typer.Exit(1)
|
|
1500
|
+
|
|
1501
|
+
if template_url is None:
|
|
1502
|
+
return
|
|
1503
|
+
|
|
1470
1504
|
create_config_init_app_from_remote_template(
|
|
1471
|
-
app_name=app_name,
|
|
1472
|
-
template_url=template_url,
|
|
1505
|
+
app_name=app_name, template_url=template_url
|
|
1473
1506
|
)
|
|
1474
1507
|
|
|
1475
1508
|
telemetry.send("init", template=template)
|
|
1476
1509
|
|
|
1477
1510
|
|
|
1511
|
+
def initialize_main_module_index_from_generation(app_name: str, generation_hash: str):
|
|
1512
|
+
"""Overwrite the `index` function in the main module with reflex.build generated code.
|
|
1513
|
+
|
|
1514
|
+
Args:
|
|
1515
|
+
app_name: The name of the app.
|
|
1516
|
+
generation_hash: The generation hash from reflex.build.
|
|
1517
|
+
"""
|
|
1518
|
+
# Download the reflex code for the generation.
|
|
1519
|
+
resp = httpx.get(
|
|
1520
|
+
constants.Templates.REFLEX_BUILD_CODE_URL.format(
|
|
1521
|
+
generation_hash=generation_hash
|
|
1522
|
+
)
|
|
1523
|
+
).raise_for_status()
|
|
1524
|
+
|
|
1525
|
+
def replace_content(_match):
|
|
1526
|
+
return "\n".join(
|
|
1527
|
+
[
|
|
1528
|
+
"def index() -> rx.Component:",
|
|
1529
|
+
textwrap.indent("return " + resp.text, " "),
|
|
1530
|
+
"",
|
|
1531
|
+
"",
|
|
1532
|
+
],
|
|
1533
|
+
)
|
|
1534
|
+
|
|
1535
|
+
main_module_path = Path(app_name, app_name + constants.Ext.PY)
|
|
1536
|
+
main_module_code = main_module_path.read_text()
|
|
1537
|
+
main_module_path.write_text(
|
|
1538
|
+
re.sub(
|
|
1539
|
+
r"def index\(\).*:\n([^\n]\s+.*\n+)+",
|
|
1540
|
+
replace_content,
|
|
1541
|
+
main_module_code,
|
|
1542
|
+
)
|
|
1543
|
+
)
|
|
1544
|
+
|
|
1545
|
+
|
|
1478
1546
|
def format_address_width(address_width) -> int | None:
|
|
1479
1547
|
"""Cast address width to an int.
|
|
1480
1548
|
|
|
@@ -1562,3 +1630,15 @@ def is_windows_bun_supported() -> bool:
|
|
|
1562
1630
|
and cpu_info.model_name is not None
|
|
1563
1631
|
and "ARM" not in cpu_info.model_name
|
|
1564
1632
|
)
|
|
1633
|
+
|
|
1634
|
+
|
|
1635
|
+
def is_generation_hash(template: str) -> bool:
|
|
1636
|
+
"""Check if the template looks like a generation hash.
|
|
1637
|
+
|
|
1638
|
+
Args:
|
|
1639
|
+
template: The template name.
|
|
1640
|
+
|
|
1641
|
+
Returns:
|
|
1642
|
+
True if the template is composed of 32 or more hex characters.
|
|
1643
|
+
"""
|
|
1644
|
+
return re.match(r"^[0-9a-f]{32,}$", template) is not None
|
reflex/utils/pyi_generator.py
CHANGED
|
@@ -882,6 +882,7 @@ class PyiGenerator:
|
|
|
882
882
|
# retrieve the _SUBMODULES and _SUBMOD_ATTRS from an init file if present.
|
|
883
883
|
sub_mods = getattr(mod, "_SUBMODULES", None)
|
|
884
884
|
sub_mod_attrs = getattr(mod, "_SUBMOD_ATTRS", None)
|
|
885
|
+
pyright_ignore_imports = getattr(mod, "_PYRIGHT_IGNORE_IMPORTS", [])
|
|
885
886
|
|
|
886
887
|
if not sub_mods and not sub_mod_attrs:
|
|
887
888
|
return
|
|
@@ -901,6 +902,7 @@ class PyiGenerator:
|
|
|
901
902
|
# construct the import statement and handle special cases for aliases
|
|
902
903
|
sub_mod_attrs_imports = [
|
|
903
904
|
f"from .{path} import {mod if not isinstance(mod, tuple) else mod[0]} as {mod if not isinstance(mod, tuple) else mod[1]}"
|
|
905
|
+
+ (" # type: ignore" if mod in pyright_ignore_imports else "")
|
|
904
906
|
for mod, path in sub_mod_attrs.items()
|
|
905
907
|
]
|
|
906
908
|
sub_mod_attrs_imports.append("")
|
reflex/utils/redir.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Utilities to handle redirection to browser UI."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import uuid
|
|
5
|
+
import webbrowser
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from .. import constants
|
|
10
|
+
from . import console
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def open_browser_and_wait(
|
|
14
|
+
target_url: str, poll_url: str, interval: int = 2
|
|
15
|
+
) -> httpx.Response:
|
|
16
|
+
"""Open a browser window to target_url and request poll_url until it returns successfully.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
target_url: The URL to open in the browser.
|
|
20
|
+
poll_url: The URL to poll for success.
|
|
21
|
+
interval: The interval in seconds to wait between polling.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
The response from the poll_url.
|
|
25
|
+
"""
|
|
26
|
+
if not webbrowser.open(target_url):
|
|
27
|
+
console.warn(
|
|
28
|
+
f"Unable to automatically open the browser. Please navigate to {target_url} in your browser."
|
|
29
|
+
)
|
|
30
|
+
console.info("[b]Complete the workflow in the browser to continue.[/b]")
|
|
31
|
+
while True:
|
|
32
|
+
try:
|
|
33
|
+
response = httpx.get(poll_url, follow_redirects=True)
|
|
34
|
+
if response.is_success:
|
|
35
|
+
break
|
|
36
|
+
except httpx.RequestError as err:
|
|
37
|
+
console.info(f"Will retry after error occurred while polling: {err}.")
|
|
38
|
+
time.sleep(interval)
|
|
39
|
+
return response
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def reflex_build_redirect() -> str:
|
|
43
|
+
"""Open the browser window to reflex.build and wait for the user to select a generation.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
The selected generation hash.
|
|
47
|
+
"""
|
|
48
|
+
token = str(uuid.uuid4())
|
|
49
|
+
target_url = constants.Templates.REFLEX_BUILD_URL.format(reflex_init_token=token)
|
|
50
|
+
poll_url = constants.Templates.REFLEX_BUILD_POLL_URL.format(reflex_init_token=token)
|
|
51
|
+
response = open_browser_and_wait(target_url, poll_url)
|
|
52
|
+
return response.json()["generation_hash"]
|