reflex 0.2.2__py3-none-any.whl → 0.2.3__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.

@@ -48,6 +48,6 @@ def index() -> rx.Component:
48
48
 
49
49
 
50
50
  # Add state and page to the app.
51
- app = rx.App(state=State)
51
+ app = rx.App()
52
52
  app.add_page(index, title="Counter")
53
53
  app.compile()
@@ -40,6 +40,6 @@ def index() -> rx.Component:
40
40
 
41
41
 
42
42
  # Add state and page to the app.
43
- app = rx.App(state=State)
43
+ app = rx.App()
44
44
  app.add_page(index)
45
45
  app.compile()
@@ -168,7 +168,8 @@ export const applyEvent = async (event, router, socket) => {
168
168
 
169
169
  // Send the event to the server.
170
170
  event.token = getToken();
171
- event.router_data = (({ pathname, query }) => ({ pathname, query }))(router);
171
+ event.router_data = (({ pathname, query, asPath }) => ({ pathname, query, asPath }))(router);
172
+
172
173
  if (socket) {
173
174
  socket.emit("event", JSON.stringify(event));
174
175
  return true;
@@ -382,6 +383,9 @@ export const preventDefault = (event) => {
382
383
  * @returns The value.
383
384
  */
384
385
  export const getRefValue = (ref) => {
386
+ if (!ref || !ref.current){
387
+ return;
388
+ }
385
389
  if (ref.current.type == "checkbox") {
386
390
  return ref.current.checked;
387
391
  } else {
reflex/__init__.py CHANGED
@@ -36,6 +36,7 @@ from .event import window_alert as window_alert
36
36
  from .middleware import Middleware as Middleware
37
37
  from .model import Model as Model
38
38
  from .model import session as session
39
+ from .page import page as page
39
40
  from .route import route as route
40
41
  from .state import ComputedVar as var
41
42
  from .state import State as State
reflex/app.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import asyncio
4
4
  import inspect
5
+ import os
5
6
  from multiprocessing.pool import ThreadPool
6
7
  from typing import (
7
8
  Any,
@@ -34,15 +35,17 @@ from reflex.config import get_config
34
35
  from reflex.event import Event, EventHandler, EventSpec
35
36
  from reflex.middleware import HydrateMiddleware, Middleware
36
37
  from reflex.model import Model
38
+ from reflex.page import (
39
+ DECORATED_PAGES,
40
+ )
37
41
  from reflex.route import (
38
- DECORATED_ROUTES,
39
42
  catchall_in_route,
40
43
  catchall_prefix,
41
44
  get_route_args,
42
45
  verify_route_validity,
43
46
  )
44
47
  from reflex.state import DefaultState, State, StateManager, StateUpdate
45
- from reflex.utils import format, types
48
+ from reflex.utils import console, format, types
46
49
 
47
50
  # Define custom types.
48
51
  ComponentCallable = Callable[[], Component]
@@ -100,9 +103,29 @@ class App(Base):
100
103
 
101
104
  Raises:
102
105
  ValueError: If the event namespace is not provided in the config.
106
+ Also, if there are multiple client subclasses of rx.State(Subclasses of rx.State should consist
107
+ of the DefaultState and the client app state).
103
108
  """
104
109
  super().__init__(*args, **kwargs)
110
+ state_subclasses = State.__subclasses__()
111
+ inferred_state = state_subclasses[-1]
112
+ is_testing_env = constants.PYTEST_CURRENT_TEST in os.environ
113
+
114
+ # Special case to allow test cases have multiple subclasses of rx.State.
115
+ if not is_testing_env:
116
+ # Only the default state and the client state should be allowed as subclasses.
117
+ if len(state_subclasses) > 2:
118
+ raise ValueError(
119
+ "rx.State has been subclassed multiple times. Only one subclass is allowed"
120
+ )
105
121
 
122
+ # verify that provided state is valid
123
+ if self.state not in [DefaultState, inferred_state]:
124
+ console.warn(
125
+ f"Using substate ({self.state.__name__}) as root state in `rx.App` is currently not supported."
126
+ f" Defaulting to root state: ({inferred_state.__name__})"
127
+ )
128
+ self.state = inferred_state
106
129
  # Get the config
107
130
  config = get_config()
108
131
 
@@ -414,14 +437,7 @@ class App(Base):
414
437
  )
415
438
 
416
439
  froute = format.format_route
417
- if (froute(constants.ROOT_404) not in self.pages) and (
418
- not any(page.startswith("[[...") for page in self.pages)
419
- ):
420
- self.pages[froute(constants.ROOT_404)] = component
421
- if not any(
422
- page.startswith("[...") or page.startswith("[[...") for page in self.pages
423
- ):
424
- self.pages[froute(constants.SLUG_404)] = component
440
+ self.pages[froute(constants.SLUG_404)] = component
425
441
 
426
442
  def setup_admin_dash(self):
427
443
  """Setup the admin dash."""
@@ -455,23 +471,23 @@ class App(Base):
455
471
  )
456
472
  task = progress.add_task("Compiling: ", total=len(self.pages))
457
473
 
458
- for render, kwargs in DECORATED_ROUTES:
474
+ # TODO: include all work done in progress indicator, not just self.pages
475
+ for render, kwargs in DECORATED_PAGES:
459
476
  self.add_page(render, **kwargs)
460
477
 
461
478
  # Get the env mode.
462
479
  config = get_config()
463
480
 
464
- # Empty the .web pages directory
465
- compiler.purge_web_pages_dir()
466
-
467
481
  # Store the compile results.
468
482
  compile_results = []
469
483
 
470
484
  # Compile the pages in parallel.
471
485
  custom_components = set()
486
+ # TODO Anecdotally, processes=2 works 10% faster (cpu_count=12)
472
487
  thread_pool = ThreadPool()
473
488
  with progress:
474
489
  for route, component in self.pages.items():
490
+ # TODO: this progress does not reflect actual threaded task completion
475
491
  progress.advance(task)
476
492
  component.add_style(self.style)
477
493
  compile_results.append(
@@ -493,6 +509,8 @@ class App(Base):
493
509
  # Get the results.
494
510
  compile_results = [result.get() for result in compile_results]
495
511
 
512
+ # TODO the compile tasks below may also benefit from parallelization too
513
+
496
514
  # Compile the custom components.
497
515
  compile_results.append(compiler.compile_components(custom_components))
498
516
 
@@ -503,18 +521,18 @@ class App(Base):
503
521
  compile_results.append(compiler.compile_theme(self.style))
504
522
 
505
523
  # Compile the Tailwind config.
506
- compile_results.append(
507
- compiler.compile_tailwind(
508
- dict(**config.tailwind, content=constants.TAILWIND_CONTENT)
509
- if config.tailwind is not None
510
- else {}
524
+ if config.tailwind is not None:
525
+ config.tailwind["content"] = config.tailwind.get(
526
+ "content", constants.TAILWIND_CONTENT
511
527
  )
512
- )
528
+ compile_results.append(compiler.compile_tailwind(config.tailwind))
529
+
530
+ # Empty the .web pages directory
531
+ compiler.purge_web_pages_dir()
513
532
 
514
533
  # Write the pages at the end to trigger the NextJS hot reload only once.
515
534
  thread_pool = ThreadPool()
516
535
  for output_path, code in compile_results:
517
- compiler_utils.write_page(output_path, code)
518
536
  thread_pool.apply_async(compiler_utils.write_page, args=(output_path, code))
519
537
  thread_pool.close()
520
538
  thread_pool.join()
@@ -647,9 +665,7 @@ def upload(app: App):
647
665
  update = await app.postprocess(state, event, update)
648
666
  # Send update to client
649
667
  await asyncio.create_task(
650
- app.event_namespace.emit( # type: ignore
651
- str(constants.SocketEvent.EVENT), update.json(), to=sid
652
- )
668
+ app.event_namespace.emit(str(constants.SocketEvent.EVENT), update.json(), to=sid) # type: ignore
653
669
  )
654
670
  # Set the state for the session.
655
671
  app.state_manager.set_state(event.token, state)
@@ -101,25 +101,22 @@ class DataTable(Gridjs):
101
101
 
102
102
  def _render(self) -> Tag:
103
103
  if isinstance(self.data, Var):
104
- self.columns = BaseVar(
105
- name=f"{self.data.name}.columns"
106
- if types.is_dataframe(self.data.type_)
107
- else f"{self.columns.name}",
108
- type_=List[Any],
109
- state=self.data.state,
110
- )
111
- self.data = BaseVar(
112
- name=f"{self.data.name}.data"
113
- if types.is_dataframe(self.data.type_)
114
- else f"{self.data.name}",
115
- type_=List[List[Any]],
116
- state=self.data.state,
117
- )
118
-
119
- # If given a pandas df break up the data and columns
120
- if types.is_dataframe(type(self.data)):
121
- self.columns = Var.create(list(self.data.columns.values.tolist())) # type: ignore
122
- self.data = Var.create(format.format_dataframe_values(self.data)) # type: ignore
104
+ if types.is_dataframe(self.data.type_):
105
+ self.columns = BaseVar(
106
+ name=f"{self.data.name}.columns",
107
+ type_=List[Any],
108
+ state=self.data.state,
109
+ )
110
+ self.data = BaseVar(
111
+ name=f"{self.data.name}.data",
112
+ type_=List[List[Any]],
113
+ state=self.data.state,
114
+ )
115
+ else:
116
+ # If given a pandas df break up the data and columns
117
+ if types.is_dataframe(type(self.data)):
118
+ self.columns = Var.create(list(self.data.columns.values.tolist())) # type: ignore
119
+ self.data = Var.create(format.format_dataframe_values(self.data)) # type: ignore
123
120
 
124
121
  # Render the table.
125
122
  return super()._render()
@@ -1,7 +1,9 @@
1
1
  """Breadcrumb components."""
2
2
 
3
3
  from reflex.components.component import Component
4
+ from reflex.components.layout.foreach import Foreach
4
5
  from reflex.components.libs.chakra import ChakraComponent
6
+ from reflex.components.navigation.link import Link
5
7
  from reflex.vars import Var
6
8
 
7
9
 
@@ -31,11 +33,18 @@ class Breadcrumb(ChakraComponent):
31
33
  The breadcrumb component.
32
34
  """
33
35
  if len(children) == 0:
34
- children = []
35
- for label, link in items or []:
36
- children.append(
37
- BreadcrumbItem.create(BreadcrumbLink.create(label, href=link))
38
- )
36
+ if isinstance(items, Var):
37
+ children = [
38
+ Foreach.create(
39
+ items,
40
+ lambda item: BreadcrumbItem.create(label=item[0], href=item[1]),
41
+ ),
42
+ ]
43
+
44
+ else:
45
+ children = []
46
+ for label, link in items or []:
47
+ children.append(BreadcrumbItem.create(label=label, href=link))
39
48
  return super().create(*children, **props)
40
49
 
41
50
 
@@ -56,8 +65,22 @@ class BreadcrumbItem(ChakraComponent):
56
65
  # The left and right margin applied to the separator
57
66
  spacing: Var[str]
58
67
 
59
- # The href of the item.
60
- href: Var[str]
68
+ @classmethod
69
+ def create(cls, *children, label=None, href=None, **props):
70
+ """Create a Breadcrumb Item component.
71
+
72
+ Args:
73
+ children: The children of the component.
74
+ label: The label used in the link. Defaults to None.
75
+ href: The URL of the link. Defaults to None.
76
+ props: The properties of the component.
77
+
78
+ Returns:
79
+ The BreadcrumbItem component
80
+ """
81
+ if len(children) == 0:
82
+ children = [BreadcrumbLink.create(label or "", href=href or "")] # type: ignore
83
+ return super().create(*children, **props)
61
84
 
62
85
 
63
86
  class BreadcrumbSeparator(ChakraComponent):
@@ -66,7 +89,7 @@ class BreadcrumbSeparator(ChakraComponent):
66
89
  tag = "BreadcrumbSeparator"
67
90
 
68
91
 
69
- class BreadcrumbLink(ChakraComponent):
92
+ class BreadcrumbLink(Link):
70
93
  """The breadcrumb link."""
71
94
 
72
95
  tag = "BreadcrumbLink"
@@ -33,7 +33,6 @@ class IterTag(Tag):
33
33
  return BaseVar(
34
34
  name=INDEX_VAR,
35
35
  type_=int,
36
- is_local=True,
37
36
  )
38
37
 
39
38
  @staticmethod
@@ -46,6 +45,7 @@ class IterTag(Tag):
46
45
  return BaseVar(
47
46
  name=INDEX_VAR,
48
47
  type_=int,
48
+ is_local=True,
49
49
  )
50
50
 
51
51
  @staticmethod
@@ -67,46 +67,48 @@ class Tag(Base):
67
67
  Raises:
68
68
  TypeError: If the prop is not a valid type.
69
69
  """
70
- # Handle var props.
71
- if isinstance(prop, Var):
72
- if not prop.is_local or prop.is_string:
73
- return str(prop)
74
- if types._issubclass(prop.type_, str):
75
- return format.json_dumps(prop.full_name)
76
- prop = prop.full_name
77
-
78
- # Handle event props.
79
- elif isinstance(prop, EventChain):
80
- if prop.full_control:
81
- # Full control component events.
82
- event = format.format_full_control_event(prop)
70
+ try:
71
+ # Handle var props.
72
+ if isinstance(prop, Var):
73
+ if not prop.is_local or prop.is_string:
74
+ return str(prop)
75
+ if types._issubclass(prop.type_, str):
76
+ return format.json_dumps(prop.full_name)
77
+ prop = prop.full_name
78
+
79
+ # Handle event props.
80
+ elif isinstance(prop, EventChain):
81
+ if prop.full_control:
82
+ # Full control component events.
83
+ event = format.format_full_control_event(prop)
84
+ else:
85
+ # All other events.
86
+ chain = ",".join(
87
+ [format.format_event(event) for event in prop.events]
88
+ )
89
+ event = f"Event([{chain}], {EVENT_ARG})"
90
+ prop = f"{EVENT_ARG} => {event}"
91
+
92
+ # Handle other types.
93
+ elif isinstance(prop, str):
94
+ if format.is_wrapped(prop, "{"):
95
+ return prop
96
+ return format.json_dumps(prop)
97
+
98
+ elif isinstance(prop, Figure):
99
+ prop = json.loads(to_json(prop))["data"] # type: ignore
100
+
101
+ # For dictionaries, convert any properties to strings.
102
+ elif isinstance(prop, dict):
103
+ prop = format.format_dict(prop)
104
+
83
105
  else:
84
- # All other events.
85
- chain = ",".join([format.format_event(event) for event in prop.events])
86
- event = f"Event([{chain}], {EVENT_ARG})"
87
- prop = f"{EVENT_ARG} => {event}"
88
-
89
- # Handle other types.
90
- elif isinstance(prop, str):
91
- if format.is_wrapped(prop, "{"):
92
- return prop
93
- return format.json_dumps(prop)
94
-
95
- elif isinstance(prop, Figure):
96
- prop = json.loads(to_json(prop))["data"] # type: ignore
97
-
98
- # For dictionaries, convert any properties to strings.
99
- elif isinstance(prop, dict):
100
- prop = format.format_dict(prop)
101
-
102
- else:
103
- # Dump the prop as JSON.
104
- try:
106
+ # Dump the prop as JSON.
105
107
  prop = format.json_dumps(prop)
106
- except TypeError as e:
107
- raise TypeError(
108
- f"Could not format prop: {prop} of type {type(prop)}"
109
- ) from e
108
+ except TypeError as e:
109
+ raise TypeError(
110
+ f"Could not format prop: {prop} of type {type(prop)}"
111
+ ) from e
110
112
 
111
113
  # Wrap the variable in braces.
112
114
  assert isinstance(prop, str), "The prop must be a string."
@@ -9,5 +9,8 @@ class Heading(ChakraComponent):
9
9
 
10
10
  tag = "Heading"
11
11
 
12
+ # Override the tag. The default tag is `<h2>`.
13
+ as_: Var[str]
14
+
12
15
  # "4xl" | "3xl" | "2xl" | "xl" | "lg" | "md" | "sm" | "xs"
13
16
  size: Var[str]
@@ -1,12 +1,33 @@
1
1
  """Markdown component."""
2
2
 
3
3
  import textwrap
4
- from typing import List, Union
4
+ from typing import Callable, Dict, List, Union
5
5
 
6
+ from reflex.compiler import utils
6
7
  from reflex.components.component import Component
8
+ from reflex.components.datadisplay.list import ListItem, OrderedList, UnorderedList
9
+ from reflex.components.navigation import Link
10
+ from reflex.components.typography.heading import Heading
11
+ from reflex.components.typography.text import Text
12
+ from reflex.style import Style
7
13
  from reflex.utils import types
8
14
  from reflex.vars import BaseVar, ImportVar, Var
9
15
 
16
+ # Mapping from markdown tags to components.
17
+ components_by_tag: Dict[str, Callable] = {
18
+ "h1": Heading,
19
+ "h2": Heading,
20
+ "h3": Heading,
21
+ "h4": Heading,
22
+ "h5": Heading,
23
+ "h6": Heading,
24
+ "p": Text,
25
+ "ul": UnorderedList,
26
+ "ol": OrderedList,
27
+ "li": ListItem,
28
+ "a": Link,
29
+ }
30
+
10
31
 
11
32
  class Markdown(Component):
12
33
  """A markdown component."""
@@ -17,6 +38,37 @@ class Markdown(Component):
17
38
 
18
39
  is_default = True
19
40
 
41
+ # Custom defined styles for the markdown elements.
42
+ custom_styles: Dict[str, Style] = {
43
+ k: Style(v)
44
+ for k, v in {
45
+ "h1": {
46
+ "as_": "h1",
47
+ "size": "2xl",
48
+ },
49
+ "h2": {
50
+ "as_": "h2",
51
+ "size": "xl",
52
+ },
53
+ "h3": {
54
+ "as_": "h3",
55
+ "size": "lg",
56
+ },
57
+ "h4": {
58
+ "as_": "h4",
59
+ "size": "md",
60
+ },
61
+ "h5": {
62
+ "as_": "h5",
63
+ "size": "sm",
64
+ },
65
+ "h6": {
66
+ "as_": "h6",
67
+ "size": "xs",
68
+ },
69
+ }.items()
70
+ }
71
+
20
72
  @classmethod
21
73
  def create(cls, *children, **props) -> Component:
22
74
  """Create a markdown component.
@@ -39,47 +91,45 @@ class Markdown(Component):
39
91
  return super().create(src, **props)
40
92
 
41
93
  def _get_imports(self):
94
+ from reflex.components.datadisplay.code import Code, CodeBlock
95
+
42
96
  imports = super()._get_imports()
43
- imports["@chakra-ui/react"] = {
44
- ImportVar(tag="Heading"),
45
- ImportVar(tag="Code"),
46
- ImportVar(tag="Text"),
47
- ImportVar(tag="Link"),
48
- ImportVar(tag="UnorderedList"),
49
- ImportVar(tag="OrderedList"),
50
- ImportVar(tag="ListItem"),
51
- }
52
- imports["react-syntax-highlighter"] = {ImportVar(tag="Prism", is_default=True)}
97
+
98
+ # Special markdown imports.
53
99
  imports["remark-math"] = {ImportVar(tag="remarkMath", is_default=True)}
54
100
  imports["remark-gfm"] = {ImportVar(tag="remarkGfm", is_default=True)}
55
101
  imports["rehype-katex"] = {ImportVar(tag="rehypeKatex", is_default=True)}
56
102
  imports["rehype-raw"] = {ImportVar(tag="rehypeRaw", is_default=True)}
57
103
  imports[""] = {ImportVar(tag="katex/dist/katex.min.css")}
104
+
105
+ # Get the imports for each component.
106
+ for component in components_by_tag.values():
107
+ imports = utils.merge_imports(imports, component()._get_imports())
108
+
109
+ # Get the imports for the code components.
110
+ imports = utils.merge_imports(
111
+ imports, CodeBlock.create(theme="light")._get_imports()
112
+ )
113
+ imports = utils.merge_imports(imports, Code.create()._get_imports())
58
114
  return imports
59
115
 
60
116
  def _render(self):
61
- return (
62
- super()
63
- ._render()
64
- .add_props(
65
- components={
66
- "h1": "{({node, ...props}) => <Heading size='2xl' paddingY='0.5em' {...props} />}",
67
- "h2": "{({node, ...props}) => <Heading size='xl' paddingY='0.5em' {...props} />}",
68
- "h3": "{({node, ...props}) => <Heading size='lg' paddingY='0.5em' {...props} />}",
69
- "h4": "{({node, ...props}) => <Heading size='sm' paddingY='0.5em' {...props} />}",
70
- "h5": "{({node, ...props}) => <Heading size='xs' paddingY='0.5em' {...props} />}",
71
- "ul": "{UnorderedList}",
72
- "ol": "{OrderedList}",
73
- "li": "{ListItem}",
74
- "p": "{({node, ...props}) => <Text paddingY='0.5em' {...props} />}",
75
- "a": "{Link}",
76
- "code": """{({node, inline, className, children, ...props}) =>
117
+ from reflex.components.tags.tag import Tag
118
+
119
+ components = {
120
+ tag: f"{{({{node, ...props}}) => <{(component().tag)} {{...props}} {''.join(Tag(name='', props=Style(self.custom_styles.get(tag, {}))).format_props())} />}}"
121
+ for tag, component in components_by_tag.items()
122
+ }
123
+ components[
124
+ "code"
125
+ ] = """{({node, inline, className, children, ...props}) =>
77
126
  {
78
127
  const match = (className || '').match(/language-(?<lang>.*)/);
79
128
  return !inline ? (
80
129
  <Prism
81
130
  children={String(children).replace(/\n$/, '')}
82
131
  language={match ? match[1] : ''}
132
+ theme={light}
83
133
  {...props}
84
134
  />
85
135
  ) : (
@@ -88,12 +138,18 @@ class Markdown(Component):
88
138
  </Code>
89
139
  );
90
140
  }}""".replace(
91
- "\n", " "
92
- ),
93
- },
141
+ "\n", " "
142
+ )
143
+
144
+ return (
145
+ super()
146
+ ._render()
147
+ .add_props(
148
+ components=components,
94
149
  remark_plugins=BaseVar(name="[remarkMath, remarkGfm]", type_=List[str]),
95
150
  rehype_plugins=BaseVar(
96
151
  name="[rehypeKatex, rehypeRaw]", type_=List[str]
97
152
  ),
98
153
  )
154
+ .remove_props("custom_components")
99
155
  )
reflex/config.py CHANGED
@@ -165,12 +165,6 @@ class Config(Base):
165
165
  # The environment mode.
166
166
  env: constants.Env = constants.Env.DEV
167
167
 
168
- # The path to the bun executable.
169
- bun_path: str = constants.BUN_PATH
170
-
171
- # Disable bun.
172
- disable_bun: bool = False
173
-
174
168
  # Additional frontend packages to install.
175
169
  frontend_packages: List[str] = []
176
170