solara-ui 1.39.0__py2.py3-none-any.whl → 1.40.0__py2.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.
Files changed (67) hide show
  1. solara/__init__.py +1 -1
  2. solara/components/__init__.py +1 -0
  3. solara/components/input_text_area.py +86 -0
  4. solara/components/markdown.py +1 -1
  5. solara/hooks/use_thread.py +4 -4
  6. solara/lab/components/chat.py +8 -2
  7. solara/server/assets/style.css +2 -1
  8. solara/server/flask.py +1 -1
  9. solara/server/jupyter/server_extension.py +11 -1
  10. solara/server/jupyter/solara.py +91 -0
  11. solara/server/patch.py +1 -0
  12. solara/server/pyinstaller/__init__.py +9 -0
  13. solara/server/pyinstaller/hook-ipyreact.py +5 -0
  14. solara/server/pyinstaller/hook-ipyvuetify.py +5 -0
  15. solara/server/pyinstaller/hook-solara.py +9 -0
  16. solara/server/server.py +6 -1
  17. solara/server/starlette.py +18 -6
  18. solara/server/static/highlight-dark.css +1 -1
  19. solara/server/static/main-vuetify.js +1 -1
  20. solara/server/static/solara_bootstrap.py +1 -1
  21. solara/server/templates/solara.html.j2 +30 -6
  22. solara/website/assets/custom.css +20 -57
  23. solara/website/components/__init__.py +2 -2
  24. solara/website/components/algolia_api.vue +23 -6
  25. solara/website/components/breadcrumbs.py +28 -0
  26. solara/website/components/contact.py +144 -0
  27. solara/website/components/docs.py +11 -9
  28. solara/website/components/header.py +31 -20
  29. solara/website/components/markdown.py +12 -1
  30. solara/website/components/markdown_nav.vue +34 -0
  31. solara/website/components/sidebar.py +7 -1
  32. solara/website/pages/__init__.py +87 -254
  33. solara/website/pages/about/__init__.py +9 -0
  34. solara/website/pages/about/about.md +3 -0
  35. solara/website/pages/careers/__init__.py +27 -0
  36. solara/website/pages/changelog/__init__.py +2 -2
  37. solara/website/pages/changelog/changelog.md +12 -0
  38. solara/website/pages/contact/__init__.py +30 -6
  39. solara/website/pages/documentation/__init__.py +25 -33
  40. solara/website/pages/documentation/advanced/content/10-howto/40-embed.md +2 -1
  41. solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +1 -1
  42. solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +2 -1
  43. solara/website/pages/documentation/advanced/content/30-enterprise/00-overview.md +1 -1
  44. solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +5 -2
  45. solara/website/pages/documentation/api/hooks/use_thread.md +6 -0
  46. solara/website/pages/documentation/components/data/pivot_table.py +2 -2
  47. solara/website/pages/documentation/components/input/input.py +2 -0
  48. solara/website/pages/documentation/components/output/sql_code.py +3 -3
  49. solara/website/pages/documentation/examples/__init__.py +2 -2
  50. solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +2 -2
  51. solara/website/pages/documentation/getting_started/content/05-fundamentals/10-components.md +19 -14
  52. solara/website/pages/documentation/getting_started/content/05-fundamentals/50-state-management.md +205 -15
  53. solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +3 -1
  54. solara/website/pages/home.vue +1199 -0
  55. solara/website/pages/our_team/__init__.py +83 -0
  56. solara/website/pages/pricing/__init__.py +31 -0
  57. solara/website/pages/roadmap/__init__.py +11 -0
  58. solara/website/pages/roadmap/roadmap.md +41 -0
  59. solara/website/pages/scale_ipywidgets.py +45 -0
  60. {solara_ui-1.39.0.dist-info → solara_ui-1.40.0.dist-info}/METADATA +2 -2
  61. {solara_ui-1.39.0.dist-info → solara_ui-1.40.0.dist-info}/RECORD +65 -49
  62. solara/website/components/hero.py +0 -15
  63. solara/website/pages/contact/contact.md +0 -17
  64. {solara_ui-1.39.0.data → solara_ui-1.40.0.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
  65. {solara_ui-1.39.0.data → solara_ui-1.40.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
  66. {solara_ui-1.39.0.dist-info → solara_ui-1.40.0.dist-info}/WHEEL +0 -0
  67. {solara_ui-1.39.0.dist-info → solara_ui-1.40.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,6 +2,6 @@
2
2
 
3
3
  Solara enterprise is a not Open Source, but the source code is visible, is free for non-commercial use and installable from pypi.
4
4
 
5
- For commercial use, you require a license. Please [contact](/contact) us for more information.
5
+ For commercial use, you require a license. Please see [pricing](/pricing) for more information.
6
6
 
7
7
  Solara-enterprise will stay free for non-commercial use and features that are in solara will **not be moved to solara-enterprise**.
@@ -44,12 +44,15 @@ from solara_enterprise import auth
44
44
  @solara.component
45
45
  def Page():
46
46
  if not auth.user.value:
47
- solara.Button("Login", icon_name="mdi-login", href=auth.get_login_url())
47
+ # `get_login_url` should automatically redirect the user back to the current page.
48
+ # The URL argument is only used here as an example.
49
+ solara.Button("Login", icon_name="mdi-login", href=auth.get_login_url("documentation/advanced/enterprise/oauth"))
48
50
  else:
49
51
  userinfo = auth.user.value['userinfo']
50
52
  if 'name' in userinfo:
51
53
  solara.Markdown(f"### Welcome {userinfo['name']}")
52
- solara.Button("Logout", icon_name="mdi-logout", href=auth.get_logout_url())
54
+ # See above comment
55
+ solara.Button("Logout", icon_name="mdi-logout", href=auth.get_logout_url("documentation/advanced/enterprise/oauth"))
53
56
  ```
54
57
  ## How to configure OAuth
55
58
 
@@ -56,3 +56,9 @@ graph TD;
56
56
  RUNNING-- "work finished" -->FINISHED
57
57
  RUNNING-- "cancel()" -->CANCELLED
58
58
  ```
59
+
60
+ ## Future deprecation
61
+
62
+ Note that [`use_task`](/https://solara.dev/documentation/components/lab/use_task) is an alternative to `use_thread` that will most likely
63
+ replace `use_thread` in the future. Compared to `use_thread`, `use_task` has no intrusive cancel, which turned out to be too large of
64
+ a performance overhead to be useful. Also, `use_task` can run async functions, which is not possible with `use_thread`.
@@ -54,10 +54,10 @@ def Page():
54
54
  return solara.Markdown("This example requires vaex, please install it with `pip install vaex`")
55
55
  with solara.Div() as main:
56
56
  solara.Markdown("# Titanic")
57
- selected, on_selected = solara.use_state({"x": [0, 0]})
57
+ selected, on_selected = solara.use_state({"x": [0, 0]}) # noqa: SH101
58
58
  solara.provide_cross_filter()
59
59
  with solara.VBox():
60
- type, set_type = solara.use_state("view")
60
+ type, set_type = solara.use_state("view") # noqa: SH101
61
61
  with solara.ToggleButtonsSingle(type, on_value=set_type):
62
62
  solara.Button("PivotTableView", value="view")
63
63
  solara.Button("PivotTable", value="df")
@@ -13,6 +13,8 @@ Page = NoPage
13
13
 
14
14
 
15
15
  __doc__ += apidoc(solara.InputText.f) # type: ignore
16
+ __doc__ += "# InputTextArea"
17
+ __doc__ += apidoc(solara.InputTextArea.f) # type: ignore
16
18
  __doc__ += "# InputFloat"
17
19
  __doc__ += apidoc(solara.InputFloat.f) # type: ignore
18
20
  __doc__ += "# InputInt"
@@ -42,8 +42,8 @@ if vaex is not None:
42
42
  def Page():
43
43
  if vaex is None:
44
44
  return solara.Error("Vaex is not installed, run pip install vaex-core vaex-hdf5")
45
- query, set_query = solara.use_state("SELECT * from titanic")
46
- query_executed, set_query_executed = solara.use_state(cast(Optional[str], None))
45
+ query, set_query = solara.use_state("SELECT * from titanic") # noqa: SH101
46
+ query_executed, set_query_executed = solara.use_state(cast(Optional[str], None)) # noqa: SH101
47
47
 
48
48
  def run_query(cancel: threading.Event) -> pd.DataFrame:
49
49
  if not query_executed:
@@ -55,7 +55,7 @@ def Page():
55
55
  df = pd.DataFrame(cursor.fetchall(), columns=[k[0] for k in cursor.description])
56
56
  return df
57
57
 
58
- result: solara.Result[pd.DataFrame] = solara.use_thread(run_query, dependencies=[query_executed])
58
+ result: solara.Result[pd.DataFrame] = solara.use_thread(run_query, dependencies=[query_executed]) # noqa: SH101
59
59
  with solara.VBox() as main:
60
60
  solara.SqlCode(query=query, tables=table_hints, on_query=set_query)
61
61
  enable_execute = (query != query_executed) or result.error is not None
@@ -1,5 +1,5 @@
1
1
  import solara
2
- from solara.website.components import Gallery
2
+ from solara.website.components import Gallery, MarkdownWithMetadata
3
3
 
4
4
  title = "Examples"
5
5
 
@@ -34,7 +34,7 @@ def Layout(children):
34
34
  doc = module.__doc__
35
35
  if doc:
36
36
  with solara.VBox(grow=True):
37
- solara.Markdown(doc)
37
+ MarkdownWithMetadata(doc)
38
38
  with solara.HBox():
39
39
  if route_current.path != "/":
40
40
  solara.Button("View source code on GitHub", icon_name="mdi-github-circle", href=github_url, class_="ma-2", target="_blank", text=True)
@@ -35,7 +35,7 @@
35
35
  "\n",
36
36
  "We will use a subsample of the [San Fanfrisco crime dataset](https://www.kaggle.com/competitions/sf-crime/data) which contains information on types of crimes and where they were committed.\n",
37
37
  "\n",
38
- "[Download the CSV file](https://raw.githubusercontent.com/widgetti/solara/master/solara/website/pages/docs/content/04-tutorial/SF_crime_sample.csv.gz) if you want to run this locally, or let the code below sort it out."
38
+ "[Download the CSV file](https://github.com/widgetti/solara/raw/master/solara/website/pages/documentation/getting_started/content/04-tutorials/SF_crime_sample.csv.gz) if you want to run this locally, or let the code below sort it out."
39
39
  ]
40
40
  },
41
41
  {
@@ -350,7 +350,7 @@
350
350
  "\n",
351
351
  "ROOT = Path(solara.__file__).parent / \"website\" / \"pages\" / \"docs\" / \"content\" / \"04-tutorial\"\n",
352
352
  "path = ROOT / Path(\"SF_crime_sample.csv.gz\")\n",
353
- "url = \"https://raw.githubusercontent.com/widgetti/solara/master/solara/website/pages/docs/content/04-tutorial/SF_crime_sample.csv\"\n",
353
+ "url = \"https://github.com/widgetti/solara/raw/master/solara/website/pages/documentation/getting_started/content/04-tutorials/SF_crime_sample.csv.gz\"\n",
354
354
  "\n",
355
355
  "if path.exists():\n",
356
356
  " df_crime = pd.read_csv(path)\n",
@@ -41,7 +41,7 @@ By utilizing both Widget Components and Function Components, you can create flex
41
41
  In Solara, users can create their own custom components without any special distinction from the built-in components provided by the framework, they are all components. These user-defined components have the same capabilities and can be composed seamlessly alongside Solara's components, allowing for the creation of highly customized and reusable user interfaces.
42
42
 
43
43
  # Defining Components
44
- To create a component in Solara, you'll start by defining a Python function decorated with @solara.component. Inside the function, you can create the component's structure by calling Solara's built-in components or creating custom components to suit your specific needs. If a single element is created, it's taken as the component's main element. If multiple elements are created, they are automatically wrapped in a Column component.
44
+ To create a component in Solara, you'll start by defining a Python function decorated with `@solara.component`. Inside the function, you can create the component's structure by calling Solara's built-in components or creating custom components to suit your specific needs. If a single element is created, it's taken as the component's main element. If multiple elements are created, they are automatically wrapped in a [Column component](/documentation/components/layout/column).
45
45
 
46
46
  Here's an example of a simple Solara component that displays a button:
47
47
 
@@ -69,7 +69,7 @@ def MyApp():
69
69
  MyButton()
70
70
  ```
71
71
 
72
- In this example, we create a `MyApp` function decorated with `@solara.component`. The function create two MyButton elements, resulting in two buttons (or two component instances).
72
+ In this example, we create a `MyApp` function decorated with `@solara.component`. The function creates two MyButton elements, resulting in two buttons (or two component instances).
73
73
 
74
74
  ## Handling User Interactions
75
75
  Components in Solara can capture user input and respond to events, such as button clicks or form submissions. To handle user interactions, you'll define callback functions and connect them to your components using Solara's event handling system.
@@ -87,7 +87,7 @@ def MyInteractiveButton():
87
87
  solara.Button("Click me!", on_click=on_button_click)
88
88
  ```
89
89
 
90
- In this example, we define a function called on_button_click that will be executed when the button is clicked. In the MyInteractiveButton function, we create a Button component and set the on_click argument to the on_button_click function.
90
+ In this example, we define a function called `on_button_click` that will be executed when the button is clicked. In the `MyInteractiveButton` function, we create a Button component and set the `on_click` argument to the `on_button_click` function.
91
91
 
92
92
  By following these steps, you can create and use components to build rich, interactive applications with Solara.
93
93
 
@@ -111,7 +111,7 @@ def MyButton(text):
111
111
  In this example, we define a function called MyButton that takes a single argument, text. The render function creates a Button component from Solara with the specified text.
112
112
 
113
113
  ### Using Application state in Components
114
- To manage the state of a component in Solara, you can use the solara.reactive() function to create reactive variables. Reactive variables are used to store values that can change over time and automatically trigger component updates when their values change. This allows you to create components that respond to changes in data and user interactions.
114
+ To manage the state of a component in Solara, you can use the [`solara.reactive()`](/documentation/api/utilities/reactive) function to create reactive variables. Reactive variables are used to store values that can change over time and automatically trigger component updates when their values change. This allows you to create components that respond to changes in data and user interactions.
115
115
 
116
116
  Here's an example that demonstrates the use of reactive variables in Solara components:
117
117
  ```solara
@@ -119,22 +119,25 @@ import solara
119
119
 
120
120
  counter = solara.reactive(0)
121
121
 
122
+
122
123
  def increment():
123
124
  counter.value += 1
124
125
 
126
+
125
127
  @solara.component
126
128
  def CounterDisplay():
127
129
  solara.Info(f"Counter: {counter.value}")
128
130
 
131
+
129
132
  @solara.component
130
133
  def IncrementButton():
131
134
  solara.Button("Increment", on_click=increment)
132
135
 
136
+
133
137
  @solara.component
134
138
  def Page():
135
139
  IncrementButton()
136
140
  CounterDisplay()
137
-
138
141
  ```
139
142
 
140
143
  In this example, we create a reactive variable counter with an initial value of 0. We define two components: `CounterDisplay` and `IncrementButton`. `CounterDisplay` renders the current value of counter, while `IncrementButton` increments the value of counter when clicked. Whenever the counter value changes, `CounterDisplay` automatically updates to display the new value.
@@ -143,33 +146,35 @@ By using arguments and state in your Solara components, you can create more dyna
143
146
 
144
147
  ### Internal State in Components
145
148
 
146
- In addition to using reactive variables for global or application-wide state, you can also manage internal or component-specific state using the use_state hook in Solara. The use_state hook allows you to define state variables that are local to a component, and automatically trigger updates when their values change.
149
+ In addition to using reactive variables for global or application-wide state, you can also manage internal or component-specific state using the [`use_reactive`](/documentation/api/hooks/use_reactive) hook in Solara. The `use_reactive` hook allows you to define reactive variables which are local to a component, and automatically trigger updates when their values change.
147
150
 
148
- To use the use_state hook, call the solara.use_state() function inside your component function. This function takes an initial value as an argument and returns a tuple containing the current state value and a function to update the state.
151
+ To use the `use_reactive` hook, call the solara.use_reactive() function inside your component function. This function takes an initial value as an argument and returns a the same reactive variable on each render.
149
152
 
150
- Here's an example that demonstrates the use of the use_state hook to manage internal state in a Solara component:
153
+ Here's an example that demonstrates the use of the use_reactive hook to manage internal state in a Solara component:
151
154
 
152
155
  ```solara
153
156
  import solara
154
157
 
158
+
155
159
  @solara.component
156
160
  def Counter():
157
- count, set_count = solara.use_state(0)
161
+ count = solara.use_reactive(0)
158
162
 
159
163
  def increment():
160
- set_count(count + 1)
164
+ count.value += 1
161
165
 
162
166
  solara.Button("Increment", on_click=increment)
163
- solara.Info(f"Counter: {count}")
167
+ solara.Info(f"Counter: {count.value}")
168
+
164
169
 
165
170
  @solara.component
166
171
  def Page():
167
172
  Counter()
168
173
  ```
169
174
 
170
- In this example, we define a Counter component that uses the use_state hook to manage its internal state. We create a state variable count with an initial value of 0 and a function set_count to update the state. The increment function increments the value of count when the button is clicked. Whenever the count value changes, the component automatically updates to display the new value.
175
+ In this example, we define a Counter component that uses the `use_reactive` hook to manage its internal state. We create a reactive variable called `count` with an initial value of 0 and a function `increment` to update the state. The increment function increments the value of count when the button is clicked. Whenever the count value changes, the component automatically updates to display the new value.
171
176
 
172
- By using the use_state hook, you can manage the internal state of your components and create more dynamic and interactive applications that respond to user input and changes in data.
177
+ By using the `use_reactive` hook, you can manage the internal state of your components and create more dynamic and interactive applications that respond to user input and changes in data.
173
178
 
174
179
  ## Lazy Rendering in Solara Components
175
180
 
@@ -216,7 +221,7 @@ This example demonstrates Solara's lazy rendering, where only the relevant compo
216
221
 
217
222
 
218
223
  ## Conclusions
219
- In conclusion, understanding components, their arguments, and how to manage their internal state is crucial for building Solara applications. To create more advanced components, you need to have a deeper understanding of hooks, such as the use_state hook we have already discussed.
224
+ In conclusion, understanding components, their arguments, and how to manage their internal state is crucial for building Solara applications. To create more advanced components, you need to have a deeper understanding of hooks, such as the `use_reactive` hook we have already discussed.
220
225
 
221
226
  In the next fundamentals article, we will explore more hooks available in Solara, which will enable you to build more sophisticated components that cater to a wide range of use cases. By learning about hooks, you can create powerful components that can manage state, interact with other components, and respond to user input.
222
227
 
@@ -33,21 +33,26 @@ def Page():
33
33
 
34
34
  In this case, the `SomeAppSpecificComponent` is not reusable in the sense that a second component has a different state. The `color` variable is global and shared across all components. This component is meant to be used only once, and mainly helps to organize the code.
35
35
 
36
- ## Local component state using solara.use_state
36
+ You may have heard that globals are considered a bad practice. As with many things, it depends on the context. A possible downside of using a global is that it does not
37
+ allow you to create multiple instances of the same component with different states. But if the state reflects application state, there is by definition only one instance
38
+ of it needed.
37
39
 
38
- [`solara.use_state`](/documentation/api/hooks/use_state) is a hook that allows you to manage local state within a specific component. This approach is beneficial when you want to encapsulate state within a component, making it self-contained and modular. Local state management is suitable for situations where state changes only affect the component and do not need to be shared across the application.
40
+ ### Local component state using solara.use_reactive
39
41
 
40
- Example:
41
- ```solara
42
+ If you do need state that is specific to a component, you should use [`solara.use_reactive`](/documentation/api/hooks/use_reactive) hook. This hook allow you to create local state variables that are scoped to a specific component. This approach is useful when you want to encapsulate state within a component, making it self-contained and modular. Local state management is suitable for situations where state changes only affect the component and do not need to be shared across the application.
43
+
44
+
45
+ ```solara hl_lines="6 8"
42
46
  import solara
43
47
 
48
+
44
49
  @solara.component
45
50
  def ReusableComponent():
46
- # color = solara.use_reactive("red") # another possibility
47
- color, set_color = solara.use_state("red") # local state
51
+ color = solara.use_reactive("red") # local state (instead of top level solara.reactive)
48
52
  solara.Select(label="Color",values=["red", "green", "blue", "orange"],
49
- value=color, on_value=set_color)
50
- solara.Markdown("### Solara is awesome", style={"color": color})
53
+ value=color)
54
+ solara.Markdown("### Solara is awesome", style={"color": color.value})
55
+
51
56
 
52
57
  @solara.component
53
58
  def Page():
@@ -57,22 +62,27 @@ def Page():
57
62
 
58
63
  ```
59
64
 
60
- ## Local component state using solara.use_reactive
65
+ ### Local component state using solara.use_state (not recommended)
61
66
 
67
+ [`solara.use_state`](/documentation/api/hooks/use_state) is a hook that might be a bit more familiar to React developers. It also allows you to create local state variables that are scoped to a specific component, however, instead of using reactive variables, it uses a tuple of a value and a setter function.
62
68
 
63
- `use_reactive` is the middle ground between `use_state` and `reactive`. It allows you to create a reactive variable that is scoped to a specific component. This is more a matter of taste, we generally recommend using `use_reactive`, but if you prefer a little less magic, you can use `use_state` instead.
69
+ We generally recommend using `use_reactive` over `use_state` as it is more easy to refactor between global application state and local component state by switching between `use_reactive` and `reactive`. There is no equivalent for `use_state` at the global level.
64
70
 
71
+ If we take the previous example and replace `use_reactive` by `use_state`, we get:
65
72
 
66
- If we take the previous example using `use_state`, are replace `use_state` by `use_reactive`, we get:
67
- ```solara
73
+ Example:
74
+ ```solara hl_lines="6 9"
68
75
  import solara
69
76
 
77
+
70
78
  @solara.component
71
79
  def ReusableComponent():
72
- color = solara.use_reactive("red") # another possibility
80
+ # color = solara.use_reactive("red") # instead of use_reactive (not recommended)
81
+ color, set_color = solara.use_state("red") # local state
73
82
  solara.Select(label="Color",values=["red", "green", "blue", "orange"],
74
- value=color)
75
- solara.Markdown("### Solara is awesome", style={"color": color.value})
83
+ value=color, on_value=set_color)
84
+ solara.Markdown("### Solara is awesome", style={"color": color})
85
+
76
86
 
77
87
  @solara.component
78
88
  def Page():
@@ -82,6 +92,186 @@ def Page():
82
92
 
83
93
  ```
84
94
 
95
+ ## Mutation pittfalls
96
+
97
+ In Python, strings, numbers, and tuples are immutable. This means that you cannot change the value of a string, number, or tuple in place.
98
+ Instead, you need to create a new object and assign that to a variable.
99
+
100
+ ```python
101
+ a = 1
102
+ b = a
103
+ # a.value = 2 # ERROR: numbers are immutable
104
+ a = 2 # instead, re-assign a new value, the number 1 will not change
105
+ assert b == 1 # b is still 1
106
+ ```
107
+ However, many objects in Python are mutable, including lists and dictionaries or potentially user defined classes. This means that you can change the value of a list, dictionary, or user defined class in place without creating a new object.
108
+ ```python
109
+ a = [1, 2, 3]
110
+ b = a # b points to the same list as a
111
+ a.append(4) # a is now [1, 2, 3, 4]
112
+ assert b == [1, 2, 3, 4] # b is also [1, 2, 3, 4]
113
+ ```
114
+
115
+ ### Not mutating lists
116
+
117
+ However, mutations in Python are not observable. **This means that if you change the value of a list, dictionary, or user defined class, Solara does not know that the value has changed and does not know it needs to re-render a component that uses that value.**
118
+
119
+ ```python
120
+ import solara
121
+
122
+ reactive_list = solara.reactive([1, 2, 3])
123
+ # The next line will not trigger a re-render of a component
124
+ reactive_list.value.append(4) # ERROR: mutating a list is not observable in Python
125
+ ```
126
+
127
+ Although Solara could potentially track mutations of lists and dictionaries, that would be difficult to do for user defined classes, since we would need to know which methods mutate the object and which do not. Therefore, we have chosen not to include
128
+ any magic tracking of mutations in Solara, and instead require you to re-assign a new value to a reactive variable if you want to trigger a re-render.
129
+
130
+ ```python hl_lines="5"
131
+ import solara
132
+
133
+ reactive_list = solara.reactive([1, 2, 3])
134
+ # Instead, re-assign a new value
135
+ reactive_list.value = [*reactive_list.value, 4] # GOOD: re-assign a new list
136
+ ```
137
+
138
+ ### Not mutating dictionaries
139
+
140
+ A similar pattern applies to dictionaries.
141
+
142
+ ```python
143
+ import solara
144
+
145
+ reactive_dict = solara.reactive({"a": 1, "b": 2})
146
+ reactive_dict.value = {**reactive_dict.value, "c": 3} # GOOD: re-assign a new dictionary
147
+ # deleting a key
148
+ reactive_dict.value = {k: v for k, v in reactive_dict.value.items() if k != "a"} # GOOD: re-assign a new dictionary
149
+ # deleting a key (method 2)
150
+ dict_copy = reactive_dict.value.copy()
151
+ del dict_copy["b"]
152
+ reactive_dict.value = dict_copy # GOOD: re-assign a new dictionary
153
+ ```
154
+
155
+ ### Not mutating user defined classes
156
+
157
+ Or user defined classes, like a Pandas DataFrame.
158
+
159
+ ```python
160
+ import solara
161
+ import pandas as pd
162
+
163
+ reactive_df = solara.reactive(pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}))
164
+ # reactive_df.value.append({"a": 4, "b": 7}) # BAD: mutating a DataFrame is not observable in Python
165
+ df_copy = reactive_df.value.copy(deep=True) # for Pandas 3, deep=True is not necessary
166
+ df_copy = df_copy.append({"a": 4, "b": 7}, ignore_index=True)
167
+ reactive_df.value = df_copy # GOOD: re-assign a new DataFrame
168
+ ```
169
+
170
+
171
+ ## Creating a store
172
+
173
+ Using reactive variables is a powerful way to manage state in your Solara applications. However, as your application grows, you may find that you need a more structured way to manage your state.
174
+
175
+ In larger applications, you may want to create a store to manage your application's state. A store is a regular Python class where all attributes are reactive variables.
176
+
177
+ In your Python class you are free to expose the reactive variables as attributes, or you can create properties to make certain attributes read-only or to add additional logic when setting an attribute.
178
+
179
+ A complete TODO application demonstrates this below.
180
+
181
+ ```solara
182
+ import uuid
183
+ from typing import Callable
184
+
185
+ import solara
186
+
187
+
188
+ # this todo item is only a collection of reactive values
189
+ class TodoItem:
190
+ def __init__(self, text: str, done: bool = False):
191
+ self.text = solara.reactive(text)
192
+ self.done = solara.reactive(done)
193
+ self._uuid = solara.reactive(str(uuid.uuid4()))
194
+ self._dirty = solara.reactive(True)
195
+
196
+ def __str__(self) -> str:
197
+ return f"{self.text.value} ({'done' if self.done else 'not done'})"
198
+
199
+
200
+ # However, this class really adds some logic to the todo items
201
+ class TodoStore:
202
+ def __init__(self, items: list[TodoItem]):
203
+ # we keep the items as a protected attribute
204
+ self._items = solara.reactive(items)
205
+ self.add_item_text = solara.reactive("")
206
+
207
+ @property
208
+ def items(self):
209
+ # and make the items read only for a property
210
+ return self._items.value
211
+
212
+ def add_item(self, item):
213
+ self._items.value = [*self._items.value, item]
214
+ # reset the new text after adding a new item
215
+ self.add_item_text.value = ""
216
+
217
+ def add(self):
218
+ self.add_item(TodoItem(text=self.add_item_text.value))
219
+
220
+ def remove(self, item: TodoItem):
221
+ self._items.value = [k for k in self.items if k._uuid.value != item._uuid.value]
222
+
223
+ @property
224
+ def done_count(self):
225
+ return len([k for k in self.items if k.done.value])
226
+
227
+ @property
228
+ def done_percentage(self):
229
+ if len(self.items) == 0:
230
+ return 0
231
+ else:
232
+ return self.done_count / len(self.items) * 100
233
+
234
+
235
+ @solara.component
236
+ def TodoItemCard(item: TodoItem, on_remove: Callable[[TodoItem], None]):
237
+ with solara.Card():
238
+ solara.InputText("", value=item.text)
239
+ solara.Switch(label="Done", value=item.done)
240
+ solara.Button("Remove", on_click=lambda: on_remove(item))
241
+
242
+
243
+ # The TodoApp component is reusable, so in the future
244
+ # we could have multiple TodoApp components if needed
245
+ # (e.g. multiple lists of todos)
246
+
247
+ default_store = TodoStore(
248
+ [
249
+ TodoItem(text="Write a blog post", done=False),
250
+ TodoItem(text="Take out the trash", done=True),
251
+ TodoItem(text="Do the laundry", done=False),
252
+ ]
253
+ )
254
+
255
+
256
+ @solara.component
257
+ def TodoApp(store: TodoStore = default_store):
258
+ for item in store.items:
259
+ TodoItemCard(item, on_remove=store.remove)
260
+
261
+ with solara.Card("New item"):
262
+ solara.InputText(label="Text", value=store.add_item_text)
263
+ solara.Button("Add new", on_click=store.add)
264
+ solara.ProgressLinear(value=store.done_percentage)
265
+
266
+
267
+ @solara.component
268
+ def Page():
269
+ TodoApp()
270
+ ```
271
+
272
+
273
+
274
+
85
275
  ## Conclusion
86
276
  Understanding the advantages and disadvantages of reusable components and application-specific code can help you strike the right balance between modularity and simplicity when building your Solara applications.
87
277
 
@@ -65,7 +65,7 @@ A typical command would be:
65
65
  $ SOLARA_APP=sol.py gunicorn --workers 4 --threads=20 -b 0.0.0.0:8765 solara.server.flask:app
66
66
  ```
67
67
 
68
- Note that we need at least 1 thread per user due to the use of a websocket.
68
+ Note that we need at least 1 thread per user due to the use of a websocket. *Make sure you understand the implications of using multiple workers by reading [about handling multiple workers with solara server](https://solara.dev/documentation/advanced/understanding/solara-server#handling-multiple-workers)*
69
69
 
70
70
  ### Embedding in an existing Flask application
71
71
 
@@ -98,6 +98,8 @@ For [Starlette](https://www.starlette.io/) we will assume [uvicorn](http://www.u
98
98
  $ SOLARA_APP=sol.py uvicorn --workers 4 --host 0.0.0.0 --port 8765 solara.server.starlette:app
99
99
  ```
100
100
 
101
+ *Make sure you understand the implications of using multiple workers by reading [about handling multiple workers with solara server](https://solara.dev/documentation/advanced/understanding/solara-server#handling-multiple-workers)*
102
+
101
103
  ### Embedding in an existing Starlette application
102
104
 
103
105
  If you already have a Starlette app and want to add your Solara app behind a prefix, say `'/solara/'`, you can mount the Starlette routes for Solara to your existing app as follows.