reflex 0.8.0a7__py3-none-any.whl → 0.8.1a2__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 (36) hide show
  1. reflex/.templates/web/utils/state.js +18 -1
  2. reflex/.templates/web/vite-plugin-safari-cachebust.js +160 -0
  3. reflex/.templates/web/vite.config.js +28 -6
  4. reflex/app.py +1 -1
  5. reflex/components/__init__.py +1 -0
  6. reflex/components/__init__.pyi +2 -0
  7. reflex/components/component.py +53 -1
  8. reflex/components/core/banner.py +3 -13
  9. reflex/components/core/upload.py +5 -5
  10. reflex/components/core/upload.pyi +2 -2
  11. reflex/components/el/__init__.py +7 -1
  12. reflex/components/el/__init__.pyi +2 -1
  13. reflex/components/radix/primitives/accordion.py +10 -29
  14. reflex/components/radix/primitives/accordion.pyi +7 -1
  15. reflex/components/radix/themes/typography/link.py +1 -30
  16. reflex/components/radix/themes/typography/link.pyi +1 -304
  17. reflex/components/react_router/__init__.py +5 -0
  18. reflex/components/react_router/dom.py +69 -0
  19. reflex/components/react_router/dom.pyi +321 -0
  20. reflex/components/recharts/recharts.py +2 -2
  21. reflex/constants/compiler.py +1 -1
  22. reflex/constants/installer.py +2 -2
  23. reflex/environment.py +46 -0
  24. reflex/event.py +18 -1
  25. reflex/istate/data.py +142 -69
  26. reflex/plugins/tailwind_v4.py +2 -2
  27. reflex/state.py +3 -3
  28. reflex/utils/exec.py +27 -5
  29. reflex/utils/lazy_loader.py +7 -1
  30. reflex/utils/processes.py +27 -11
  31. reflex/utils/pyi_generator.py +17 -2
  32. {reflex-0.8.0a7.dist-info → reflex-0.8.1a2.dist-info}/METADATA +2 -2
  33. {reflex-0.8.0a7.dist-info → reflex-0.8.1a2.dist-info}/RECORD +36 -32
  34. {reflex-0.8.0a7.dist-info → reflex-0.8.1a2.dist-info}/WHEEL +0 -0
  35. {reflex-0.8.0a7.dist-info → reflex-0.8.1a2.dist-info}/entry_points.txt +0 -0
  36. {reflex-0.8.0a7.dist-info → reflex-0.8.1a2.dist-info}/licenses/LICENSE +0 -0
reflex/istate/data.py CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  import dataclasses
4
4
  from collections.abc import Mapping
5
+ from typing import TYPE_CHECKING
6
+ from urllib.parse import _NetlocResultMixinStr, urlsplit
5
7
 
6
8
  from reflex import constants
7
- from reflex.utils import format
9
+ from reflex.utils import console, format
8
10
  from reflex.utils.serializers import serializer
9
11
 
10
12
 
@@ -45,36 +47,40 @@ class _HeaderData:
45
47
  )
46
48
 
47
49
 
48
- @dataclasses.dataclass(frozen=True, init=False)
50
+ _HEADER_DATA_FIELDS = frozenset(
51
+ [field.name for field in dataclasses.fields(_HeaderData)]
52
+ )
53
+
54
+
55
+ @dataclasses.dataclass(frozen=True)
49
56
  class HeaderData(_HeaderData):
50
57
  """An object containing headers data."""
51
58
 
52
- def __init__(self, router_data: dict | None = None):
53
- """Initialize the HeaderData object based on router_data.
59
+ @classmethod
60
+ def from_router_data(cls, router_data: dict) -> "HeaderData":
61
+ """Create a HeaderData object from the given router_data.
54
62
 
55
63
  Args:
56
64
  router_data: the router_data dict.
65
+
66
+ Returns:
67
+ A HeaderData object initialized with the provided router_data.
57
68
  """
58
- super().__init__()
59
- if router_data:
60
- fields_names = [f.name for f in dataclasses.fields(self)]
61
- for k, v in router_data.get(constants.RouteVar.HEADERS, {}).items():
62
- snake_case_key = format.to_snake_case(k)
63
- if snake_case_key in fields_names:
64
- object.__setattr__(self, snake_case_key, v)
65
- object.__setattr__(
66
- self,
67
- "raw_headers",
68
- _FrozenDictStrStr(
69
- **{
70
- k: v
71
- for k, v in router_data.get(
72
- constants.RouteVar.HEADERS, {}
73
- ).items()
74
- if v
75
- }
76
- ),
77
- )
69
+ return cls(
70
+ **{
71
+ snake_case_key: v
72
+ for k, v in router_data.get(constants.RouteVar.HEADERS, {}).items()
73
+ if v
74
+ and (snake_case_key := format.to_snake_case(k)) in _HEADER_DATA_FIELDS
75
+ },
76
+ raw_headers=_FrozenDictStrStr(
77
+ **{
78
+ k: v
79
+ for k, v in router_data.get(constants.RouteVar.HEADERS, {}).items()
80
+ if v
81
+ }
82
+ ),
83
+ )
78
84
 
79
85
 
80
86
  @serializer(to=dict)
@@ -90,6 +96,35 @@ def serialize_frozen_dict_str_str(obj: _FrozenDictStrStr) -> dict:
90
96
  return dict(obj._data)
91
97
 
92
98
 
99
+ class ReflexURL(str, _NetlocResultMixinStr):
100
+ """A class representing a URL split result."""
101
+
102
+ if TYPE_CHECKING:
103
+ scheme: str
104
+ netloc: str
105
+ path: str
106
+ query: str
107
+ fragment: str
108
+
109
+ def __new__(cls, url: str):
110
+ """Create a new ReflexURL instance.
111
+
112
+ Args:
113
+ url: the URL to split.
114
+
115
+ Returns:
116
+ A new ReflexURL instance.
117
+ """
118
+ (scheme, netloc, path, query, fragment) = urlsplit(url)
119
+ obj = super().__new__(cls, url)
120
+ object.__setattr__(obj, "scheme", scheme)
121
+ object.__setattr__(obj, "netloc", netloc)
122
+ object.__setattr__(obj, "path", path)
123
+ object.__setattr__(obj, "query", query)
124
+ object.__setattr__(obj, "fragment", fragment)
125
+ return obj
126
+
127
+
93
128
  @dataclasses.dataclass(frozen=True)
94
129
  class PageData:
95
130
  """An object containing page data."""
@@ -101,39 +136,30 @@ class PageData:
101
136
  full_raw_path: str = ""
102
137
  params: dict = dataclasses.field(default_factory=dict)
103
138
 
104
- def __init__(self, router_data: dict | None = None):
105
- """Initialize the PageData object based on router_data.
139
+ @classmethod
140
+ def from_router_data(cls, router_data: dict) -> "PageData":
141
+ """Create a PageData object from the given router_data.
106
142
 
107
143
  Args:
108
144
  router_data: the router_data dict.
145
+
146
+ Returns:
147
+ A PageData object initialized with the provided router_data.
109
148
  """
110
- if router_data:
111
- object.__setattr__(
112
- self,
113
- "host",
114
- router_data.get(constants.RouteVar.HEADERS, {}).get("origin", ""),
115
- )
116
- object.__setattr__(
117
- self, "path", router_data.get(constants.RouteVar.PATH, "")
118
- )
119
- object.__setattr__(
120
- self, "raw_path", router_data.get(constants.RouteVar.ORIGIN, "")
121
- )
122
- object.__setattr__(self, "full_path", f"{self.host}{self.path}")
123
- object.__setattr__(self, "full_raw_path", f"{self.host}{self.raw_path}")
124
- object.__setattr__(
125
- self, "params", router_data.get(constants.RouteVar.QUERY, {})
126
- )
127
- else:
128
- object.__setattr__(self, "host", "")
129
- object.__setattr__(self, "path", "")
130
- object.__setattr__(self, "raw_path", "")
131
- object.__setattr__(self, "full_path", "")
132
- object.__setattr__(self, "full_raw_path", "")
133
- object.__setattr__(self, "params", {})
149
+ host = router_data.get(constants.RouteVar.HEADERS, {}).get("origin", "")
150
+ path = router_data.get(constants.RouteVar.PATH, "")
151
+ raw_path = router_data.get(constants.RouteVar.ORIGIN, "")
152
+ return cls(
153
+ host=host,
154
+ path=path,
155
+ raw_path=raw_path,
156
+ full_path=f"{host}{path}",
157
+ full_raw_path=f"{host}{raw_path}",
158
+ params=router_data.get(constants.RouteVar.QUERY, {}),
159
+ )
134
160
 
135
161
 
136
- @dataclasses.dataclass(frozen=True, init=False)
162
+ @dataclasses.dataclass(frozen=True)
137
163
  class SessionData:
138
164
  """An object containing session data."""
139
165
 
@@ -141,37 +167,84 @@ class SessionData:
141
167
  client_ip: str = ""
142
168
  session_id: str = ""
143
169
 
144
- def __init__(self, router_data: dict | None = None):
145
- """Initialize the SessionData object based on router_data.
170
+ @classmethod
171
+ def from_router_data(cls, router_data: dict) -> "SessionData":
172
+ """Create a SessionData object from the given router_data.
146
173
 
147
174
  Args:
148
175
  router_data: the router_data dict.
176
+
177
+ Returns:
178
+ A SessionData object initialized with the provided router_data.
149
179
  """
150
- if router_data:
151
- client_token = router_data.get(constants.RouteVar.CLIENT_TOKEN, "")
152
- client_ip = router_data.get(constants.RouteVar.CLIENT_IP, "")
153
- session_id = router_data.get(constants.RouteVar.SESSION_ID, "")
154
- else:
155
- client_token = client_ip = session_id = ""
156
- object.__setattr__(self, "client_token", client_token)
157
- object.__setattr__(self, "client_ip", client_ip)
158
- object.__setattr__(self, "session_id", session_id)
180
+ return cls(
181
+ client_token=router_data.get(constants.RouteVar.CLIENT_TOKEN, ""),
182
+ client_ip=router_data.get(constants.RouteVar.CLIENT_IP, ""),
183
+ session_id=router_data.get(constants.RouteVar.SESSION_ID, ""),
184
+ )
159
185
 
160
186
 
161
- @dataclasses.dataclass(frozen=True, init=False)
187
+ @dataclasses.dataclass(frozen=True)
162
188
  class RouterData:
163
189
  """An object containing RouterData."""
164
190
 
165
191
  session: SessionData = dataclasses.field(default_factory=SessionData)
166
192
  headers: HeaderData = dataclasses.field(default_factory=HeaderData)
167
- page: PageData = dataclasses.field(default_factory=PageData)
193
+ _page: PageData = dataclasses.field(default_factory=PageData)
194
+ url: ReflexURL = dataclasses.field(default=ReflexURL(""))
195
+ route_id: str = ""
168
196
 
169
- def __init__(self, router_data: dict | None = None):
170
- """Initialize the RouterData object.
197
+ @property
198
+ def page(self) -> PageData:
199
+ """Get the page data.
200
+
201
+ Returns:
202
+ The PageData object.
203
+ """
204
+ console.deprecate(
205
+ "RouterData.page",
206
+ "Use RouterData.url instead",
207
+ deprecation_version="0.8.1",
208
+ removal_version="0.9.0",
209
+ )
210
+ return self._page
211
+
212
+ @classmethod
213
+ def from_router_data(cls, router_data: dict) -> "RouterData":
214
+ """Create a RouterData object from the given router_data.
171
215
 
172
216
  Args:
173
217
  router_data: the router_data dict.
218
+
219
+ Returns:
220
+ A RouterData object initialized with the provided router_data.
174
221
  """
175
- object.__setattr__(self, "session", SessionData(router_data))
176
- object.__setattr__(self, "headers", HeaderData(router_data))
177
- object.__setattr__(self, "page", PageData(router_data))
222
+ return cls(
223
+ session=SessionData.from_router_data(router_data),
224
+ headers=HeaderData.from_router_data(router_data),
225
+ _page=PageData.from_router_data(router_data),
226
+ url=ReflexURL(
227
+ router_data.get(constants.RouteVar.HEADERS, {}).get("origin", "")
228
+ + router_data.get(constants.RouteVar.ORIGIN, "")
229
+ ),
230
+ route_id=router_data.get(constants.RouteVar.PATH, ""),
231
+ )
232
+
233
+
234
+ @serializer(to=dict)
235
+ def serialize_router_data(obj: RouterData) -> dict:
236
+ """Serialize a RouterData object to a dict.
237
+
238
+ Args:
239
+ obj: the RouterData object.
240
+
241
+ Returns:
242
+ A dict representation of the RouterData object.
243
+ """
244
+ return {
245
+ "session": obj.session,
246
+ "headers": obj.headers,
247
+ "page": obj._page,
248
+ "url": obj.url,
249
+ "route_id": obj.route_id,
250
+ }
@@ -17,7 +17,7 @@ class Constants(SimpleNamespace):
17
17
  """Tailwind constants."""
18
18
 
19
19
  # The Tailwindcss version
20
- VERSION = "tailwindcss@4.1.10"
20
+ VERSION = "tailwindcss@4.1.11"
21
21
  # The Tailwind config.
22
22
  CONFIG = "tailwind.config.js"
23
23
  # Default Tailwind content paths
@@ -156,7 +156,7 @@ class TailwindV4Plugin(TailwindPlugin):
156
156
  return [
157
157
  *super().get_frontend_development_dependencies(**context),
158
158
  Constants.VERSION,
159
- "@tailwindcss/postcss@4.1.10",
159
+ "@tailwindcss/postcss@4.1.11",
160
160
  ]
161
161
 
162
162
  def pre_compile(self, **context):
reflex/state.py CHANGED
@@ -1204,7 +1204,7 @@ class BaseState(EvenMoreBasicBaseState):
1204
1204
 
1205
1205
  def argsingle_factory(param: str):
1206
1206
  def inner_func(self: BaseState) -> str:
1207
- return self.router.page.params.get(param, "")
1207
+ return self.router._page.params.get(param, "")
1208
1208
 
1209
1209
  inner_func.__name__ = param
1210
1210
 
@@ -1212,7 +1212,7 @@ class BaseState(EvenMoreBasicBaseState):
1212
1212
 
1213
1213
  def arglist_factory(param: str):
1214
1214
  def inner_func(self: BaseState) -> list[str]:
1215
- return self.router.page.params.get(param, [])
1215
+ return self.router._page.params.get(param, [])
1216
1216
 
1217
1217
  inner_func.__name__ = param
1218
1218
 
@@ -2466,7 +2466,7 @@ class OnLoadInternalState(State):
2466
2466
  """
2467
2467
  # Do not app._compile()! It should be already compiled by now.
2468
2468
  load_events = prerequisites.get_and_validate_app().app.get_load_events(
2469
- self.router.page.path
2469
+ self.router._page.path
2470
2470
  )
2471
2471
  if not load_events:
2472
2472
  self.is_hydrated = True
reflex/utils/exec.py CHANGED
@@ -573,15 +573,37 @@ def run_uvicorn_backend_prod(host: str, port: int, loglevel: LogLevel):
573
573
  port: The app port
574
574
  loglevel: The log level.
575
575
  """
576
+ import os
577
+ import shlex
578
+
576
579
  from reflex.utils import processes
577
580
 
578
581
  app_module = get_app_instance()
579
582
 
580
- command = (
581
- ["uvicorn", *("--host", host), *("--port", str(port)), "--factory", app_module]
582
- if constants.IS_WINDOWS
583
- else ["gunicorn", "--preload", *("--bind", f"{host}:{port}"), f"{app_module}()"]
584
- )
583
+ if constants.IS_WINDOWS:
584
+ command = [
585
+ "uvicorn",
586
+ *("--host", host),
587
+ *("--port", str(port)),
588
+ "--factory",
589
+ app_module,
590
+ ]
591
+ else:
592
+ # Parse GUNICORN_CMD_ARGS for user overrides
593
+ env_args = []
594
+ if gunicorn_cmd_args := os.environ.get("GUNICORN_CMD_ARGS", ""):
595
+ env_args = shlex.split(gunicorn_cmd_args)
596
+
597
+ # Our default args, then env args (env args win on conflicts)
598
+ command = [
599
+ "gunicorn",
600
+ "--preload",
601
+ "--worker-class",
602
+ "uvicorn.workers.UvicornH11Worker",
603
+ *("--bind", f"{host}:{port}"),
604
+ *env_args,
605
+ f"{app_module}()",
606
+ ]
585
607
 
586
608
  command += [
587
609
  *("--log-level", loglevel.value),
@@ -27,6 +27,7 @@ def attach(
27
27
  package_name: str,
28
28
  submodules: set[str] | None = None,
29
29
  submod_attrs: dict[str, list[str]] | None = None,
30
+ **extra_mappings,
30
31
  ):
31
32
  """Replaces a package's __getattr__, __dir__, and __all__ attributes using lazy.attach.
32
33
  The lazy loader __getattr__ doesn't support tuples as list values. We needed to add
@@ -39,6 +40,7 @@ def attach(
39
40
  submodules : List of submodules to attach.
40
41
  submod_attrs : Dictionary of submodule -> list of attributes / functions.
41
42
  These attributes are imported as they are used.
43
+ extra_mappings: Additional mappings to resolve lazily.
42
44
 
43
45
  Returns:
44
46
  __getattr__, __dir__, __all__
@@ -60,9 +62,13 @@ def attach(
60
62
  attr: mod for mod, attrs in submod_attrs.items() for attr in attrs
61
63
  }
62
64
 
63
- __all__ = sorted(submodules | attr_to_modules.keys())
65
+ __all__ = sorted([*(submodules | attr_to_modules.keys()), *(extra_mappings or [])])
64
66
 
65
67
  def __getattr__(name: str): # noqa: N807
68
+ if name in extra_mappings:
69
+ submod_path, attr = extra_mappings[name].rsplit(".", 1)
70
+ submod = importlib.import_module(submod_path)
71
+ return getattr(submod, attr)
66
72
  if name in submodules:
67
73
  return importlib.import_module(f"{package_name}.{name}")
68
74
  if name in attr_to_modules:
reflex/utils/processes.py CHANGED
@@ -15,6 +15,7 @@ from pathlib import Path
15
15
  from typing import Any, Literal, overload
16
16
 
17
17
  import click
18
+ import rich.markup
18
19
  from redis.exceptions import RedisError
19
20
  from rich.progress import Progress
20
21
 
@@ -52,6 +53,26 @@ def get_num_workers() -> int:
52
53
  return (os.cpu_count() or 1) * 2 + 1
53
54
 
54
55
 
56
+ def _is_address_responsive(
57
+ address_family: socket.AddressFamily | int, address: str, port: int
58
+ ) -> bool:
59
+ """Check if a given address and port are responsive.
60
+
61
+ Args:
62
+ address_family: The address family (e.g., socket.AF_INET or socket.AF_INET6).
63
+ address: The address to check.
64
+ port: The port to check.
65
+
66
+ Returns:
67
+ Whether the address and port are responsive.
68
+ """
69
+ try:
70
+ with closing(socket.socket(address_family, socket.SOCK_STREAM)) as sock:
71
+ return sock.connect_ex((address, port)) == 0
72
+ except OSError:
73
+ return False
74
+
75
+
55
76
  def is_process_on_port(port: int) -> bool:
56
77
  """Check if a process is running on the given port.
57
78
 
@@ -61,16 +82,11 @@ def is_process_on_port(port: int) -> bool:
61
82
  Returns:
62
83
  Whether a process is running on the given port.
63
84
  """
64
- # Test IPv4 localhost (127.0.0.1)
65
- with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
66
- ipv4_result = sock.connect_ex(("127.0.0.1", port)) == 0
67
-
68
- # Test IPv6 localhost (::1)
69
- with closing(socket.socket(socket.AF_INET6, socket.SOCK_STREAM)) as sock:
70
- ipv6_result = sock.connect_ex(("::1", port)) == 0
71
-
72
- # Port is in use if either IPv4 or IPv6 is listening
73
- return ipv4_result or ipv6_result
85
+ return _is_address_responsive( # Test IPv4 localhost (127.0.0.1)
86
+ socket.AF_INET, "127.0.0.1", port
87
+ ) or _is_address_responsive(
88
+ socket.AF_INET6, "::1", port
89
+ ) # Test IPv6 localhost (::1)
74
90
 
75
91
 
76
92
  def change_port(port: int, _type: str) -> int:
@@ -281,7 +297,7 @@ def stream_logs(
281
297
  return
282
298
  try:
283
299
  for line in process.stdout:
284
- console.debug(line, end="", progress=progress)
300
+ console.debug(rich.markup.escape(line), end="", progress=progress)
285
301
  logs.append(line)
286
302
  yield line
287
303
  except ValueError:
@@ -1108,11 +1108,13 @@ class PyiGenerator:
1108
1108
  sub_mod_attrs: dict[str, list[str | tuple[str, str]]] | None = getattr(
1109
1109
  mod, "_SUBMOD_ATTRS", None
1110
1110
  )
1111
+ extra_mappings: dict[str, str] | None = getattr(mod, "_EXTRA_MAPPINGS", None)
1111
1112
 
1112
- if not sub_mods and not sub_mod_attrs:
1113
+ if not sub_mods and not sub_mod_attrs and not extra_mappings:
1113
1114
  return None
1114
1115
  sub_mods_imports = []
1115
1116
  sub_mod_attrs_imports = []
1117
+ extra_mappings_imports = []
1116
1118
 
1117
1119
  if sub_mods:
1118
1120
  sub_mods_imports = [f"from . import {mod}" for mod in sorted(sub_mods)]
@@ -1140,7 +1142,20 @@ class PyiGenerator:
1140
1142
  ]
1141
1143
  sub_mod_attrs_imports.append("")
1142
1144
 
1143
- text = "\n" + "\n".join([*sub_mods_imports, *sub_mod_attrs_imports])
1145
+ if extra_mappings:
1146
+ for alias, import_path in extra_mappings.items():
1147
+ module_name, import_name = import_path.rsplit(".", 1)
1148
+ extra_mappings_imports.append(
1149
+ f"from {module_name} import {import_name} as {alias}"
1150
+ )
1151
+
1152
+ text = (
1153
+ "\n"
1154
+ + "\n".join(
1155
+ [*sub_mods_imports, *sub_mod_attrs_imports, *extra_mappings_imports]
1156
+ )
1157
+ + "\n"
1158
+ )
1144
1159
  text += ast.unparse(new_tree) + "\n\n"
1145
1160
  text += f"__all__ = {getattr(mod, '__all__', [])!r}\n"
1146
1161
  return text
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reflex
3
- Version: 0.8.0a7
3
+ Version: 0.8.1a2
4
4
  Summary: Web apps in pure Python.
5
5
  Project-URL: homepage, https://reflex.dev
6
6
  Project-URL: repository, https://github.com/reflex-dev/reflex
@@ -31,7 +31,7 @@ Requires-Dist: pydantic<3.0,>=1.10.21
31
31
  Requires-Dist: python-multipart<1.0,>=0.0.20
32
32
  Requires-Dist: python-socketio<6.0,>=5.12.0
33
33
  Requires-Dist: redis<7.0,>=5.2.1
34
- Requires-Dist: reflex-hosting-cli>=0.1.47
34
+ Requires-Dist: reflex-hosting-cli>=0.1.51
35
35
  Requires-Dist: rich<15,>=13
36
36
  Requires-Dist: sqlmodel<0.1,>=0.0.24
37
37
  Requires-Dist: typing-extensions>=4.13.0