reflex 0.8.7__py3-none-any.whl → 0.8.8a1__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 (66) hide show
  1. reflex/app.py +13 -5
  2. reflex/app_mixins/lifespan.py +8 -2
  3. reflex/compiler/compiler.py +12 -12
  4. reflex/compiler/templates.py +629 -102
  5. reflex/compiler/utils.py +29 -20
  6. reflex/components/base/bare.py +17 -0
  7. reflex/components/component.py +37 -33
  8. reflex/components/core/cond.py +6 -12
  9. reflex/components/core/foreach.py +1 -1
  10. reflex/components/core/match.py +83 -60
  11. reflex/components/dynamic.py +3 -3
  12. reflex/components/el/elements/forms.py +31 -14
  13. reflex/components/el/elements/forms.pyi +0 -5
  14. reflex/components/lucide/icon.py +2 -1
  15. reflex/components/lucide/icon.pyi +2 -1
  16. reflex/components/markdown/markdown.py +2 -2
  17. reflex/components/radix/primitives/accordion.py +1 -1
  18. reflex/components/radix/primitives/drawer.py +1 -1
  19. reflex/components/radix/primitives/form.py +1 -1
  20. reflex/components/radix/primitives/slider.py +1 -1
  21. reflex/components/tags/cond_tag.py +14 -5
  22. reflex/components/tags/iter_tag.py +0 -26
  23. reflex/components/tags/match_tag.py +15 -6
  24. reflex/components/tags/tag.py +3 -6
  25. reflex/components/tags/tagless.py +14 -0
  26. reflex/constants/base.py +0 -2
  27. reflex/constants/installer.py +4 -4
  28. reflex/custom_components/custom_components.py +202 -15
  29. reflex/experimental/client_state.py +1 -1
  30. reflex/istate/manager.py +2 -1
  31. reflex/plugins/shared_tailwind.py +87 -62
  32. reflex/plugins/tailwind_v3.py +2 -2
  33. reflex/plugins/tailwind_v4.py +4 -4
  34. reflex/state.py +5 -1
  35. reflex/utils/format.py +2 -3
  36. reflex/utils/frontend_skeleton.py +2 -2
  37. reflex/utils/imports.py +18 -0
  38. reflex/utils/pyi_generator.py +10 -2
  39. reflex/utils/telemetry.py +4 -1
  40. reflex/utils/templates.py +1 -6
  41. {reflex-0.8.7.dist-info → reflex-0.8.8a1.dist-info}/METADATA +3 -4
  42. {reflex-0.8.7.dist-info → reflex-0.8.8a1.dist-info}/RECORD +45 -66
  43. reflex/.templates/jinja/app/rxconfig.py.jinja2 +0 -9
  44. reflex/.templates/jinja/custom_components/README.md.jinja2 +0 -9
  45. reflex/.templates/jinja/custom_components/__init__.py.jinja2 +0 -1
  46. reflex/.templates/jinja/custom_components/demo_app.py.jinja2 +0 -39
  47. reflex/.templates/jinja/custom_components/pyproject.toml.jinja2 +0 -25
  48. reflex/.templates/jinja/custom_components/src.py.jinja2 +0 -57
  49. reflex/.templates/jinja/web/package.json.jinja2 +0 -27
  50. reflex/.templates/jinja/web/pages/_app.js.jinja2 +0 -62
  51. reflex/.templates/jinja/web/pages/_document.js.jinja2 +0 -9
  52. reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -21
  53. reflex/.templates/jinja/web/pages/component.js.jinja2 +0 -2
  54. reflex/.templates/jinja/web/pages/custom_component.js.jinja2 +0 -22
  55. reflex/.templates/jinja/web/pages/index.js.jinja2 +0 -18
  56. reflex/.templates/jinja/web/pages/macros.js.jinja2 +0 -38
  57. reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +0 -15
  58. reflex/.templates/jinja/web/pages/stateful_components.js.jinja2 +0 -5
  59. reflex/.templates/jinja/web/pages/utils.js.jinja2 +0 -93
  60. reflex/.templates/jinja/web/styles/styles.css.jinja2 +0 -6
  61. reflex/.templates/jinja/web/utils/context.js.jinja2 +0 -129
  62. reflex/.templates/jinja/web/utils/theme.js.jinja2 +0 -1
  63. reflex/.templates/jinja/web/vite.config.js.jinja2 +0 -74
  64. {reflex-0.8.7.dist-info → reflex-0.8.8a1.dist-info}/WHEEL +0 -0
  65. {reflex-0.8.7.dist-info → reflex-0.8.8a1.dist-info}/entry_points.txt +0 -0
  66. {reflex-0.8.7.dist-info → reflex-0.8.8a1.dist-info}/licenses/LICENSE +0 -0
@@ -167,7 +167,7 @@ class ClientStateVar(Var):
167
167
  ] = None
168
168
  imports.update(_refs_import)
169
169
  return cls(
170
- _js_expr="",
170
+ _js_expr="null",
171
171
  _setter_name=setter_name,
172
172
  _getter_name=var_name,
173
173
  _id_name=id_name,
reflex/istate/manager.py CHANGED
@@ -667,7 +667,8 @@ class StateManagerRedis(StateManager):
667
667
  _substate_key(client_token, substate),
668
668
  substate,
669
669
  lock_id,
670
- )
670
+ ),
671
+ name=f"reflex_set_state|{client_token}|{substate.get_full_name()}",
671
672
  )
672
673
  for substate in state.substates.values()
673
674
  ]
@@ -1,12 +1,11 @@
1
1
  """Tailwind CSS configuration types for Reflex plugins."""
2
2
 
3
3
  import dataclasses
4
+ from collections.abc import Mapping
4
5
  from copy import deepcopy
5
6
  from typing import Any, Literal, TypedDict
6
7
 
7
- from typing_extensions import NotRequired
8
-
9
- from reflex.utils.decorator import once
8
+ from typing_extensions import NotRequired, Unpack
10
9
 
11
10
  from .base import Plugin as PluginBase
12
11
 
@@ -80,71 +79,97 @@ class TailwindConfig(TypedDict):
80
79
  plugins: NotRequired[list[TailwindPluginConfig]]
81
80
 
82
81
 
83
- @once
84
- def tailwind_config_js_template():
85
- """Get the Tailwind config template.
82
+ def tailwind_config_js_template(
83
+ *, default_content: list[str], **kwargs: Unpack[TailwindConfig]
84
+ ):
85
+ """Generate a Tailwind CSS configuration file in JavaScript format.
86
+
87
+ Args:
88
+ default_content: The default content to use if none is provided.
89
+ **kwargs: The template variables.
86
90
 
87
91
  Returns:
88
92
  The Tailwind config template.
89
93
  """
90
- from reflex.compiler.templates import from_string
91
-
92
- source = r"""
93
- {# Extract destructured imports from plugin dicts only #}
94
- {%- set imports = [] %}
95
-
96
- {%- for plugin in plugins if plugin is mapping and plugin.import is defined %}
97
- {%- set _ = imports.append(plugin.import) %}
98
- {%- endfor %}
99
-
100
- {%- for imp in imports %}
101
- import { {{ imp.name }} } from {{ imp.from | tojson }};
102
- {%- endfor %}
103
-
104
- {%- for plugin in plugins %}
105
- {% if plugin is mapping and plugin.call is not defined %}
106
- import plugin{{ loop.index }} from {{ plugin.name | tojson }};
107
- {%- elif plugin is not mapping %}
108
- import plugin{{ loop.index }} from {{ plugin | tojson }};
109
- {%- endif %}
110
- {%- endfor %}
111
-
112
- {%- for preset in presets %}
113
- import preset{{ loop.index }} from {{ preset | tojson }};
114
- {%- endfor %}
115
-
116
- export default {
117
- content: {{ (content if content is defined else DEFAULT_CONTENT) | tojson }},
118
- {% if theme is defined %}theme: {{ theme | tojson }},{% else %}theme: {},{% endif %}
119
- {% if darkMode is defined %}darkMode: {{ darkMode | tojson }},{% endif %}
120
- {% if corePlugins is defined %}corePlugins: {{ corePlugins | tojson }},{% endif %}
121
- {% if important is defined %}important: {{ important | tojson }},{% endif %}
122
- {% if prefix is defined %}prefix: {{ prefix | tojson }},{% endif %}
123
- {% if separator is defined %}separator: {{ separator | tojson }},{% endif %}
124
- {% if presets is defined %}
125
- presets: [
126
- {% for preset in presets %}
127
- preset{{ loop.index }},
128
- {% endfor %}
129
- ],
130
- {% endif %}
131
- plugins: [
132
- {% for plugin in plugins %}
133
- {% if plugin is mapping and plugin.call is defined %}
134
- {{ plugin.call }}(
135
- {%- if plugin.args is defined -%}
136
- {{ plugin.args | tojson }}
137
- {%- endif -%}
138
- ),
139
- {% else %}
140
- plugin{{ loop.index }},
141
- {% endif %}
142
- {% endfor %}
94
+ import json
95
+
96
+ # Extract parameters
97
+ plugins = kwargs.get("plugins", [])
98
+ presets = kwargs.get("presets", [])
99
+ content = kwargs.get("content")
100
+ theme = kwargs.get("theme")
101
+ dark_mode = kwargs.get("darkMode")
102
+ core_plugins = kwargs.get("corePlugins")
103
+ important = kwargs.get("important")
104
+ prefix = kwargs.get("prefix")
105
+ separator = kwargs.get("separator")
106
+
107
+ # Extract destructured imports from plugin dicts only
108
+ imports = [
109
+ plugin["import"]
110
+ for plugin in plugins
111
+ if isinstance(plugin, Mapping) and "import" in plugin
143
112
  ]
144
- };
145
- """
146
113
 
147
- return from_string(source)
114
+ # Generate import statements for destructured imports
115
+ import_lines = "\n".join(
116
+ [
117
+ f"import {{ {imp['name']} }} from {json.dumps(imp['from'])};"
118
+ for imp in imports
119
+ ]
120
+ )
121
+
122
+ # Generate plugin imports
123
+ plugin_imports = []
124
+ for i, plugin in enumerate(plugins, 1):
125
+ if isinstance(plugin, Mapping) and "call" not in plugin:
126
+ plugin_imports.append(
127
+ f"import plugin{i} from {json.dumps(plugin['name'])};"
128
+ )
129
+ elif not isinstance(plugin, Mapping):
130
+ plugin_imports.append(f"import plugin{i} from {json.dumps(plugin)};")
131
+
132
+ plugin_imports_lines = "\n".join(plugin_imports)
133
+
134
+ presets_imports_lines = "\n".join(
135
+ [
136
+ f"import preset{i} from {json.dumps(preset)};"
137
+ for i, preset in enumerate(presets, 1)
138
+ ]
139
+ )
140
+
141
+ # Generate plugin array
142
+ plugin_list = []
143
+ for i, plugin in enumerate(plugins, 1):
144
+ if isinstance(plugin, Mapping) and "call" in plugin:
145
+ args_part = ""
146
+ if "args" in plugin:
147
+ args_part = json.dumps(plugin["args"])
148
+ plugin_list.append(f"{plugin['call']}({args_part})")
149
+ else:
150
+ plugin_list.append(f"plugin{i}")
151
+
152
+ plugin_use_str = ",".join(plugin_list)
153
+
154
+ return rf"""
155
+ {import_lines}
156
+
157
+ {plugin_imports_lines}
158
+
159
+ {presets_imports_lines}
160
+
161
+ export default {{
162
+ content: {json.dumps(content if content else default_content)},
163
+ theme: {json.dumps(theme if theme else {})},
164
+ {f"darkMode: {json.dumps(dark_mode)}," if dark_mode is not None else ""}
165
+ {f"corePlugins: {json.dumps(core_plugins)}," if core_plugins is not None else ""}
166
+ {f"importants: {json.dumps(important)}," if important is not None else ""}
167
+ {f"prefix: {json.dumps(prefix)}," if prefix is not None else ""}
168
+ {f"separator: {json.dumps(separator)}," if separator is not None else ""}
169
+ {f"presets: [{', '.join(f'preset{i}' for i in range(1, len(presets) + 1))}]," if presets else ""}
170
+ plugins: [{plugin_use_str}]
171
+ }};
172
+ """
148
173
 
149
174
 
150
175
  @dataclasses.dataclass
@@ -48,9 +48,9 @@ def compile_config(config: TailwindConfig):
48
48
  Returns:
49
49
  The compiled Tailwind config.
50
50
  """
51
- return Constants.CONFIG, tailwind_config_js_template().render(
51
+ return Constants.CONFIG, tailwind_config_js_template(
52
52
  **config,
53
- DEFAULT_CONTENT=Constants.CONTENT,
53
+ default_content=Constants.CONTENT,
54
54
  )
55
55
 
56
56
 
@@ -17,7 +17,7 @@ class Constants(SimpleNamespace):
17
17
  """Tailwind constants."""
18
18
 
19
19
  # The Tailwindcss version
20
- VERSION = "tailwindcss@4.1.11"
20
+ VERSION = "tailwindcss@4.1.12"
21
21
  # The Tailwind config.
22
22
  CONFIG = "tailwind.config.js"
23
23
  # Default Tailwind content paths
@@ -47,9 +47,9 @@ def compile_config(config: TailwindConfig):
47
47
  Returns:
48
48
  The compiled Tailwind config.
49
49
  """
50
- return Constants.CONFIG, tailwind_config_js_template().render(
50
+ return Constants.CONFIG, tailwind_config_js_template(
51
51
  **config,
52
- DEFAULT_CONTENT=Constants.CONTENT,
52
+ default_content=Constants.CONTENT,
53
53
  )
54
54
 
55
55
 
@@ -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.11",
159
+ "@tailwindcss/postcss@4.1.12",
160
160
  ]
161
161
 
162
162
  def pre_compile(self, **context):
reflex/state.py CHANGED
@@ -11,6 +11,7 @@ import functools
11
11
  import inspect
12
12
  import pickle
13
13
  import sys
14
+ import time
14
15
  import typing
15
16
  import warnings
16
17
  from collections.abc import AsyncIterator, Callable, Sequence
@@ -284,7 +285,10 @@ async def _resolve_delta(delta: Delta) -> Delta:
284
285
  for state_name, state_delta in delta.items():
285
286
  for var_name, value in state_delta.items():
286
287
  if asyncio.iscoroutine(value):
287
- tasks[state_name, var_name] = asyncio.create_task(value)
288
+ tasks[state_name, var_name] = asyncio.create_task(
289
+ value,
290
+ name=f"reflex_resolve_delta|{state_name}|{var_name}|{time.time()}",
291
+ )
288
292
  for (state_name, var_name), task in tasks.items():
289
293
  delta[state_name][var_name] = await task
290
294
  return delta
reflex/utils/format.py CHANGED
@@ -330,7 +330,7 @@ def format_route(route: str) -> str:
330
330
 
331
331
  def format_match(
332
332
  cond: str | Var,
333
- match_cases: list[list[Var]],
333
+ match_cases: list[tuple[list[Var], Var]],
334
334
  default: Var,
335
335
  ) -> str:
336
336
  """Format a match expression whose return type is a Var.
@@ -347,8 +347,7 @@ def format_match(
347
347
  switch_code = f"(() => {{ switch (JSON.stringify({cond})) {{"
348
348
 
349
349
  for case in match_cases:
350
- conditions = case[:-1]
351
- return_value = case[-1]
350
+ conditions, return_value = case
352
351
 
353
352
  case_conditions = " ".join(
354
353
  [f"case JSON.stringify({condition!s}):" for condition in conditions]
@@ -169,7 +169,7 @@ def _update_react_router_config(config: Config, prerender_routes: bool = False):
169
169
 
170
170
 
171
171
  def _compile_package_json():
172
- return templates.PACKAGE_JSON.render(
172
+ return templates.package_json_template(
173
173
  scripts={
174
174
  "dev": constants.PackageJson.Commands.DEV,
175
175
  "export": constants.PackageJson.Commands.EXPORT,
@@ -192,7 +192,7 @@ def _compile_vite_config(config: Config):
192
192
  base = "/"
193
193
  if frontend_path := config.frontend_path.strip("/"):
194
194
  base += frontend_path + "/"
195
- return templates.VITE_CONFIG.render(base=base)
195
+ return templates.vite_config_template(base=base)
196
196
 
197
197
 
198
198
  def initialize_vite_config():
reflex/utils/imports.py CHANGED
@@ -7,6 +7,24 @@ from collections import defaultdict
7
7
  from collections.abc import Mapping, Sequence
8
8
 
9
9
 
10
+ def merge_parsed_imports(
11
+ *imports: ImmutableParsedImportDict,
12
+ ) -> ParsedImportDict:
13
+ """Merge multiple parsed import dicts together.
14
+
15
+ Args:
16
+ *imports: The list of import dicts to merge.
17
+
18
+ Returns:
19
+ The merged import dicts.
20
+ """
21
+ all_imports: defaultdict[str, list[ImportVar]] = defaultdict(list)
22
+ for import_dict in imports:
23
+ for lib, fields in import_dict.items():
24
+ all_imports[lib].extend(fields)
25
+ return all_imports
26
+
27
+
10
28
  def merge_imports(
11
29
  *imports: ImportDict | ParsedImportDict | ParsedImportTuple,
12
30
  ) -> ParsedImportDict:
@@ -145,7 +145,7 @@ def _get_type_hint(
145
145
  res = ""
146
146
  args = get_args(value)
147
147
 
148
- if value is type(None):
148
+ if value is type(None) or value is None:
149
149
  return "None"
150
150
 
151
151
  if rx_types.is_union(value):
@@ -425,10 +425,18 @@ def type_to_ast(typ: Any, cls: type) -> ast.expr:
425
425
  Returns:
426
426
  The AST representation of the type annotation.
427
427
  """
428
- if typ is type(None):
428
+ if typ is type(None) or typ is None:
429
429
  return ast.Name(id="None")
430
430
 
431
431
  origin = get_origin(typ)
432
+ if origin is typing.Literal:
433
+ return ast.Subscript(
434
+ value=ast.Name(id="Literal"),
435
+ slice=ast.Tuple(
436
+ elts=[ast.Constant(value=val) for val in get_args(typ)], ctx=ast.Load()
437
+ ),
438
+ ctx=ast.Load(),
439
+ )
432
440
  if origin is UnionType:
433
441
  origin = typing.Union
434
442
 
reflex/utils/telemetry.py CHANGED
@@ -353,7 +353,10 @@ def send(event: str, telemetry_enabled: bool | None = None, **kwargs):
353
353
 
354
354
  try:
355
355
  # Within an event loop context, send the event asynchronously.
356
- task = asyncio.create_task(async_send(event, telemetry_enabled, **kwargs))
356
+ task = asyncio.create_task(
357
+ async_send(event, telemetry_enabled, **kwargs),
358
+ name=f"reflex_send_telemetry_event|{event}",
359
+ )
357
360
  background_tasks.add(task)
358
361
  task.add_done_callback(background_tasks.discard)
359
362
  except RuntimeError:
reflex/utils/templates.py CHANGED
@@ -1,7 +1,6 @@
1
1
  """This module provides utilities for managing Reflex app templates."""
2
2
 
3
3
  import dataclasses
4
- import re
5
4
  import shutil
6
5
  import tempfile
7
6
  import zipfile
@@ -33,12 +32,8 @@ def create_config(app_name: str):
33
32
  # Import here to avoid circular imports.
34
33
  from reflex.compiler import templates
35
34
 
36
- config_name = f"{re.sub(r'[^a-zA-Z]', '', app_name).capitalize()}Config"
37
-
38
35
  console.debug(f"Creating {constants.Config.FILE}")
39
- constants.Config.FILE.write_text(
40
- templates.RXCONFIG.render(app_name=app_name, config_name=config_name)
41
- )
36
+ constants.Config.FILE.write_text(templates.rxconfig_template(app_name=app_name))
42
37
 
43
38
 
44
39
  def initialize_app_directory(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reflex
3
- Version: 0.8.7
3
+ Version: 0.8.8a1
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
@@ -21,8 +21,7 @@ Requires-Python: <4.0,>=3.10
21
21
  Requires-Dist: alembic<2.0,>=1.15.2
22
22
  Requires-Dist: click>=8.2
23
23
  Requires-Dist: granian[reload]>=2.4.0
24
- Requires-Dist: httpx<1.0,>=0.28.0
25
- Requires-Dist: jinja2<4.0,>=3.1.2
24
+ Requires-Dist: httpx<1.0,>=0.23.3
26
25
  Requires-Dist: packaging<26,>=24.2
27
26
  Requires-Dist: platformdirs<5.0,>=4.3.7
28
27
  Requires-Dist: psutil<8.0,>=7.0.0; sys_platform == 'win32'
@@ -30,7 +29,7 @@ Requires-Dist: pydantic<3.0,>=1.10.21
30
29
  Requires-Dist: python-multipart<1.0,>=0.0.20
31
30
  Requires-Dist: python-socketio<6.0,>=5.12.0
32
31
  Requires-Dist: redis<7.0,>=5.2.1
33
- Requires-Dist: reflex-hosting-cli>=0.1.54
32
+ Requires-Dist: reflex-hosting-cli>=0.1.55
34
33
  Requires-Dist: rich<15,>=13
35
34
  Requires-Dist: sqlmodel<0.1,>=0.0.24
36
35
  Requires-Dist: starlette>=0.47.0