solara-ui 1.43.0__py2.py3-none-any.whl → 1.44.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 (37) hide show
  1. solara/__init__.py +1 -1
  2. solara/__main__.py +2 -2
  3. solara/_stores.py +114 -6
  4. solara/cache.py +6 -4
  5. solara/checks.py +1 -1
  6. solara/components/select.py +1 -1
  7. solara/components/style.py +1 -1
  8. solara/hooks/use_reactive.py +16 -1
  9. solara/lab/components/chat.py +12 -6
  10. solara/server/app.py +4 -1
  11. solara/server/kernel_context.py +2 -2
  12. solara/server/patch.py +3 -1
  13. solara/server/server.py +1 -1
  14. solara/server/starlette.py +39 -9
  15. solara/server/static/solara_bootstrap.py +1 -1
  16. solara/tasks.py +12 -1
  17. solara/test/pytest_plugin.py +4 -3
  18. solara/toestand.py +86 -26
  19. solara/website/components/docs.py +20 -1
  20. solara/website/pages/changelog/changelog.md +17 -0
  21. solara/website/pages/documentation/advanced/content/10-howto/20-layout.md +1 -1
  22. solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +4 -2
  23. solara/website/pages/documentation/components/lab/theming.py +6 -4
  24. solara/website/pages/documentation/components/output/sql_code.py +23 -25
  25. solara/website/pages/documentation/components/page/title.py +12 -14
  26. solara/website/pages/documentation/components/status/error.py +17 -18
  27. solara/website/pages/documentation/components/status/info.py +17 -18
  28. solara/website/pages/documentation/examples/__init__.py +1 -0
  29. solara/website/pages/documentation/examples/ai/chatbot.py +3 -1
  30. solara/website/pages/documentation/examples/general/live_update.py +22 -29
  31. solara/website/pages/documentation/examples/general/pokemon_search.py +1 -1
  32. {solara_ui-1.43.0.dist-info → solara_ui-1.44.0.dist-info}/METADATA +1 -1
  33. {solara_ui-1.43.0.dist-info → solara_ui-1.44.0.dist-info}/RECORD +37 -37
  34. {solara_ui-1.43.0.data → solara_ui-1.44.0.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
  35. {solara_ui-1.43.0.data → solara_ui-1.44.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
  36. {solara_ui-1.43.0.dist-info → solara_ui-1.44.0.dist-info}/WHEEL +0 -0
  37. {solara_ui-1.43.0.dist-info → solara_ui-1.44.0.dist-info}/licenses/LICENSE +0 -0
solara/toestand.py CHANGED
@@ -1,4 +1,3 @@
1
- import contextlib
2
1
  import dataclasses
3
2
  import inspect
4
3
  import logging
@@ -33,6 +32,7 @@ from solara.util import equals_extra
33
32
  import solara
34
33
  import solara.settings
35
34
  from solara import _using_solara_server
35
+ from solara.util import nullcontext
36
36
 
37
37
  T = TypeVar("T")
38
38
  TS = TypeVar("TS")
@@ -154,40 +154,63 @@ class ValueBase(Generic[T]):
154
154
  raise NotImplementedError
155
155
 
156
156
  def subscribe(self, listener: Callable[[T], None], scope: Optional[ContextManager] = None):
157
+ if scope is not None:
158
+ warnings.warn("scope argument should not be used, it was only for internal use")
159
+ del scope
157
160
  scope_id = self._get_scope_key()
158
- self.listeners[scope_id].add((listener, scope))
161
+ rc = reacton.core.get_render_context(required=False)
162
+ if _using_solara_server():
163
+ import solara.server.kernel_context
164
+
165
+ kernel = solara.server.kernel_context.get_current_context() if solara.server.kernel_context.has_current_context() else nullcontext()
166
+ else:
167
+ kernel = nullcontext()
168
+ context = Context(rc, kernel)
169
+
170
+ self.listeners[scope_id].add((listener, context))
159
171
 
160
172
  def cleanup():
161
- self.listeners[scope_id].remove((listener, scope))
173
+ self.listeners[scope_id].remove((listener, context))
162
174
 
163
175
  return cleanup
164
176
 
165
177
  def subscribe_change(self, listener: Callable[[T, T], None], scope: Optional[ContextManager] = None):
178
+ if scope is not None:
179
+ warnings.warn("scope argument should not be used, it was only for internal use")
180
+ del scope
166
181
  scope_id = self._get_scope_key()
167
- self.listeners2[scope_id].add((listener, scope))
182
+ rc = reacton.core.get_render_context(required=False)
183
+ if _using_solara_server():
184
+ import solara.server.kernel_context
185
+
186
+ kernel = solara.server.kernel_context.get_current_context() if solara.server.kernel_context.has_current_context() else nullcontext()
187
+ else:
188
+ kernel = nullcontext()
189
+ context = Context(rc, kernel)
190
+ self.listeners2[scope_id].add((listener, context))
168
191
 
169
192
  def cleanup():
170
- self.listeners2[scope_id].remove((listener, scope))
193
+ self.listeners2[scope_id].remove((listener, context))
171
194
 
172
195
  return cleanup
173
196
 
174
197
  def fire(self, new: T, old: T):
175
198
  logger.info("value change from %s to %s, will fire events", old, new)
176
199
  scope_id = self._get_scope_key()
177
- scopes = set()
178
- for listener, scope in self.listeners[scope_id].copy():
179
- if scope is not None:
180
- scopes.add(scope)
181
- for listener2, scope in self.listeners2[scope_id].copy():
182
- if scope is not None:
183
- scopes.add(scope)
184
- with contextlib.ExitStack() as stack:
185
- for scope in scopes:
186
- stack.enter_context(scope)
187
- for listener, scope in self.listeners[scope_id].copy():
188
- listener(new)
189
- for listener2, scope in self.listeners2[scope_id].copy():
190
- listener2(new, old)
200
+ contexts = set()
201
+ for listener, context in self.listeners[scope_id].copy():
202
+ contexts.add(context)
203
+ for listener2, context in self.listeners2[scope_id].copy():
204
+ contexts.add(context)
205
+ if contexts:
206
+ for context in contexts:
207
+ with context or nullcontext():
208
+ for listener, context_listener in self.listeners[scope_id].copy():
209
+ if context == context_listener:
210
+ listener(new)
211
+ for listener2, context_listener in self.listeners2[scope_id].copy():
212
+ if context == context_listener:
213
+ listener2(new, old)
191
214
 
192
215
  def update(self, _f=None, **kwargs):
193
216
  if _f is not None:
@@ -225,6 +248,9 @@ class ValueBase(Generic[T]):
225
248
 
226
249
  return cast(Callable[[TS], None], setter)
227
250
 
251
+ def _check_mutation(self):
252
+ pass
253
+
228
254
 
229
255
  # the default store for now, stores in a global dict, or when in a solara
230
256
  # context, in the solara user context
@@ -236,7 +262,7 @@ class KernelStore(ValueBase[S], ABC):
236
262
  _type_counter: Dict[Any, int] = defaultdict(int)
237
263
  scope_lock = threading.RLock()
238
264
 
239
- def __init__(self, key=None, equals: Callable[[Any, Any], bool] = equals_extra):
265
+ def __init__(self, key: str, equals: Callable[[Any, Any], bool] = equals_extra):
240
266
  super().__init__(equals=equals)
241
267
  self.storage_key = key
242
268
  self._global_dict = {}
@@ -316,7 +342,8 @@ def _is_internal_module(file_name: str):
316
342
  return (
317
343
  file_name_parts[-2:] == ["solara", "toestand.py"]
318
344
  or file_name_parts[-2:] == ["solara", "reactive.py"]
319
- or file_name_parts[-2:] == ["solara", "use_reactive.py"]
345
+ or file_name_parts[-2:] == ["solara", "_stores.py"]
346
+ or file_name_parts[-3:] == ["solara", "hooks", "use_reactive.py"]
320
347
  or file_name_parts[-2:] == ["reacton", "core.py"]
321
348
  # If we use SomeClass[K](...) we go via the typing module, so we need to skip that as well
322
349
  or (file_name_parts[-2].startswith("python") and file_name_parts[-1] == "typing.py")
@@ -381,7 +408,7 @@ reactive_df = solara.reactive(df, equals=solara.util.equals_pickle)
381
408
  code = tb.code_context[0]
382
409
  else:
383
410
  code = "<No code context available>"
384
- msg += "This warning was triggered from:\n" f"{tb.filename}:{tb.lineno}\n" f"{code}"
411
+ msg += f"This warning was triggered from:\n{tb.filename}:{tb.lineno}\n{code}"
385
412
  warnings.warn(msg)
386
413
  self._mutation_detection = False
387
414
  cls = type(default_value)
@@ -408,7 +435,7 @@ reactive_df = solara.reactive(df, equals=solara.util.equals_pickle)
408
435
  code = tb.code_context[0].strip()
409
436
  else:
410
437
  code = "No code context available"
411
- msg = f"Reactive variable was initialized at {tb.filename}:{tb.lineno} with {initial!r}, but was mutated to {current!r}.\n" f"{code}"
438
+ msg = f"Reactive variable was initialized at {tb.filename}:{tb.lineno} with {initial!r}, but was mutated to {current!r}.\n{code}"
412
439
  else:
413
440
  msg = f"Reactive variable was initialized with a value of {initial!r}, but was mutated to {current!r} (unable to report the location in the source code)."
414
441
  raise ValueError(msg)
@@ -843,7 +870,7 @@ class AutoSubscribeContextManagerBase:
843
870
  def __init__(self):
844
871
  self.subscribed = {}
845
872
 
846
- def update_subscribers(self, change_handler, scope=None):
873
+ def update_subscribers(self, change_handler):
847
874
  assert self.reactive_used is not None
848
875
  reactive_used = self.reactive_used
849
876
  # remove subfields for which we already listen to it's root reactive value
@@ -859,7 +886,7 @@ class AutoSubscribeContextManagerBase:
859
886
 
860
887
  for reactive in added:
861
888
  if reactive not in self.subscribed:
862
- unsubscribe = reactive.subscribe_change(change_handler, scope=scope)
889
+ unsubscribe = reactive.subscribe_change(change_handler)
863
890
  self.subscribed[reactive] = unsubscribe
864
891
  for reactive in removed:
865
892
  unsubscribe = self.subscribed[reactive]
@@ -881,6 +908,39 @@ class AutoSubscribeContextManagerBase:
881
908
  thread_local.reactive_used = self.reactive_used_before
882
909
 
883
910
 
911
+ class Context:
912
+ def __init__(self, render_context, kernel_context):
913
+ # combine the render context *and* the kernel context into one context
914
+ self.render_context = render_context
915
+ self.kernel_context = kernel_context
916
+
917
+ def __enter__(self):
918
+ if self.render_context is not None:
919
+ self.render_context.__enter__()
920
+ self.kernel_context.__enter__()
921
+
922
+ def __exit__(self, exc_type, exc_val, exc_tb):
923
+ if self.render_context is not None:
924
+ # this will trigger a render
925
+ res1 = self.render_context.__exit__(exc_type, exc_val, exc_tb)
926
+ else:
927
+ res1 = None
928
+ # pop the current context from the stack
929
+ res2 = self.kernel_context.__exit__(exc_type, exc_val, exc_tb)
930
+ return res1 or res2
931
+
932
+ def __eq__(self, value: object) -> bool:
933
+ if not isinstance(value, Context):
934
+ return False
935
+ return self.render_context == value.render_context and self.kernel_context == value.kernel_context
936
+
937
+ def __hash__(self) -> int:
938
+ return hash(id(self.render_context)) ^ hash(id(self.kernel_context))
939
+
940
+ def __repr__(self) -> str:
941
+ return f"Context(render_context={self.render_context}, kernel_context={self.kernel_context})"
942
+
943
+
884
944
  class AutoSubscribeContextManagerReacton(AutoSubscribeContextManagerBase):
885
945
  def __init__(self, element: solara.Element):
886
946
  self.element = element
@@ -896,7 +956,7 @@ class AutoSubscribeContextManagerReacton(AutoSubscribeContextManagerBase):
896
956
  super().__enter__()
897
957
 
898
958
  def update_subscribers():
899
- self.update_subscribers(force_update, scope=reacton.core.get_render_context(required=True))
959
+ self.update_subscribers(force_update)
900
960
 
901
961
  solara.use_effect(update_subscribers, None)
902
962
 
@@ -98,10 +98,29 @@ def WithCode(route_current):
98
98
  @solara.component
99
99
  def SubCategoryLayout(children=[]):
100
100
  route_current, all_routes = solara.use_route()
101
+ router = solara.use_router()
102
+ sibling_routes = router.path_routes[-2]
101
103
  if route_current is None:
102
104
  return solara.Error("Page not found")
103
105
  elif route_current.path == "/":
104
- return solara.Error("Not supposed to be rendered")
106
+ with solara.Column(
107
+ gap="10px", classes=["docs-card-container"], style={"flex-grow": 1, "max-width": "80%", "width": "550px", "padding-top": "64px"}, align="stretch"
108
+ ):
109
+ solara.HTML(tag="h2", unsafe_innerHTML=route_current.label, attributes={"id": route_current.path}, style="padding-left: 10%;")
110
+ for route in sibling_routes.children:
111
+ if route.path == "/":
112
+ continue
113
+ with solara.Link(route.path):
114
+ with solara.Row(
115
+ classes=["docs-card"],
116
+ style={
117
+ "background-color": "var(--docs-color-grey)",
118
+ "align-items": "center",
119
+ "height": "3rem",
120
+ },
121
+ ):
122
+ solara.HTML(tag="h3", unsafe_innerHTML=route.label, style={"color": "white", "display": "block", "flex-grow": "1", "padding": "0 24px"})
123
+ solara.v.Icon(children=["mdi-arrow-right"], color="var(--color-grey-light)", class_="docs-card-icon")
105
124
  elif route_current.module:
106
125
  WithCode(route_current)
107
126
  else:
@@ -1,5 +1,22 @@
1
1
  # Solara Changelog
2
2
 
3
+ ## Version 1.43.0
4
+ * Feature: Time picker component. [#654](https://github.com/widgetti/solara/pull/654).
5
+ * Feature: Make the default container of sibling components configurable. By default the setting remains the same (using `solara.Column`), but will default to `reacton.Fragment` in Solara 2.0 (see [the roadmap](/roadmap)). Can be changed by setting the `SOLARA_DEFAULT_CONTAINER` environmental variable to the name of a component (e.g. `"Column"`). [#928](https://github.com/widgetti/solara/pull/928).
6
+ * Feature: Do not allow reactive to be used in boolean comparisons. This feature is turned off by default, and can be enabled by setting the `SOLARA_ALLOW_REACTIVE_BOOLEAN=1` environmental variable. This feature will be enabled by default starting in Solara 2.0, see [the roadmap](/roadmap). [#846](https://github.com/widgetti/solara/pull/846).
7
+ * Feature: Use index for row names of pandas dataframes. [#613](https://github.com/widgetti/solara/pull/613).
8
+ * Feature: Support setting `http_only` for Solara session cookie. [#876](https://github.com/widgetti/solara/pull/876).
9
+ * Feature: Allow disabling notebook extensions. [#842](https://github.com/widgetti/solara/pull/842).
10
+ * Feature: `custom_exceptions` is now defined in `FakeIPython`. [#839](https://github.com/widgetti/solara/pull/839).
11
+ * Bug Fix: `InputDate` would not accept values if format was changed. [#933](https://github.com/widgetti/solara/pull/933).
12
+ * Bug Fix: Close kernels when ASGI/Starlette server is shut down. [#930](https://github.com/widgetti/solara/pull/930).
13
+ * Bug Fix: Avoid and test for the existence of memory leaks. [#377](https://github.com/widgetti/solara/pull/377).
14
+ * Bug Fix: `send_text` sent bytes instead of string. [637a77f](https://github.com/widgetti/solara/commit/637a77f2539ee68555cf998313aee62cde802579).
15
+ * Bug Fix: Avoid solara run hanging because of PyPI version request. [#855](https://github.com/widgetti/solara/pull/855).
16
+ * Bug Fix: Catch exceptions raised by startlette on websocket send failure. [7e50ee7](https://github.com/widgetti/solara/commit/7e50ee7edb7a36644b02d9d80ae91e1ac292975e).
17
+ * Bug Fix: Numpy scalars were erroneously converted to a string instead of a number. [cffccca](https://github.com/widgetti/solara/commit/cffccca500c36e21357323168b73ddd716071885).
18
+ * Bug Fix: Failing websocket.send calls would suppress all errors. [51cbfa9](https://github.com/widgetti/solara/commit/51cbfa970d42e5ff2c2b25de268e951677013467).
19
+
3
20
  ## Version 1.42.0
4
21
  * Feature: Mutation detection is now available under the `SOLARA_STORAGE_MUTATION_DETECTION` environmental variable. [#595](https://github.com/widgetti/solara/pull/595).
5
22
  * Feature: Autofocusing text inputs is now supported. [#788](https://github.com/widgetti/solara/pull/788).
@@ -122,4 +122,4 @@ The following [Container components](/documentation/advanced/understanding/conta
122
122
  * [GridDraggable](/documentation/components/layout/griddraggable)
123
123
  * [VBox](/documentation/components/layout/vbox) (kept for ipywidgets compatibility, please use Column)
124
124
  * [HBox](/documentation/components/layout/hbox) (kept for ipywidgets compatibility, please use Row)
125
- * [AppLayout](/documentation/components/layout/app_layout) Not often used directly, since Solara will already wrap your page in it. Sometimes re-used in a new `Layout` component.
125
+ * [AppLayout](/documentation/components/layout/app_layout) Not often used directly, since Solara will already wrap your page in it. Sometimes reused in a new `Layout` component.
@@ -56,7 +56,7 @@ def Page():
56
56
  ```
57
57
  ## How to configure OAuth
58
58
 
59
- Solara supports the following OAuth providers: [Auth0](https://auth0.com/) and [Fief](https://fief.dev/).
59
+ Solara currently supports [Auth0](https://auth0.com/) as the sole OAuth provider. [Fief](https://fief.dev/) support is **deprecated** (currently untested), but is not planned to be removed. If you would like support for a different provider to be added, [contact us](/contact)
60
60
 
61
61
 
62
62
  ### Configuring Auth0
@@ -134,6 +134,8 @@ To create your own Auth0 application, follow these steps:
134
134
 
135
135
  ### Configuring Fief
136
136
 
137
+ ##### Note: Fief support is not maintained or tested. If you would like Fief to be supported, feel free to [contact us](/contact)
138
+
137
139
  You can also configure Solara to use our Fief test account. To do this, you need to set the following environment variables:
138
140
 
139
141
  ```bash
@@ -164,7 +166,7 @@ Solara provides two convenient components for creating a user interface for logi
164
166
  1. [Avatar](/documentation/components/enterprise/avatar): This component shows the user's avatar.
165
167
  2. [AvatarMenu](/documentation/components/enterprise/avatar_menu): This component shows a menu with the user's avatar and a logout button.
166
168
 
167
- ## Python version support
169
+ ## Python version support
168
170
 
169
171
  Please note that Python 3.6 is not supported for Solara OAuth.
170
172
 
@@ -27,6 +27,8 @@ whether dark theme is enabled or not.
27
27
 
28
28
  ## Example
29
29
 
30
+ **Note**: Light and dark theme colors have to be set separately.
31
+
30
32
  ```solara
31
33
  import solara
32
34
  import solara.lab
@@ -35,14 +37,14 @@ from solara.lab import theme as theme
35
37
 
36
38
  def change_color(colors):
37
39
  if "purple" in colors:
38
- theme.themes.light.info = "#8617c2"
40
+ theme.themes.light.info = theme.themes.dark.info = "#8617c2"
39
41
  else:
40
- theme.themes.light.info = "#2196f3"
42
+ theme.themes.light.info = theme.themes.dark.info = "#2196f3"
41
43
 
42
44
  if "green" in colors:
43
- theme.themes.light.error = "#33bd65"
45
+ theme.themes.light.error = theme.themes.dark.error = "#33bd65"
44
46
  else:
45
- theme.themes.light.error = "#ff5252"
47
+ theme.themes.light.error = theme.themes.dark.error = "#ff5252"
46
48
 
47
49
 
48
50
  @solara.component
@@ -15,7 +15,6 @@ except ImportError:
15
15
  vaex = None
16
16
 
17
17
  import solara
18
- from solara.alias import rv
19
18
  from solara.website.utils import apidoc
20
19
 
21
20
  if vaex is not None:
@@ -56,30 +55,29 @@ def Page():
56
55
  return df
57
56
 
58
57
  result: solara.Result[pd.DataFrame] = solara.use_thread(run_query, dependencies=[query_executed]) # noqa: SH101
59
- with solara.VBox() as main:
60
- solara.SqlCode(query=query, tables=table_hints, on_query=set_query)
61
- enable_execute = (query != query_executed) or result.error is not None
62
-
63
- def execute():
64
- set_query_executed(query)
65
- if query == query_executed and result.error:
66
- result.retry() # type: ignore
67
-
68
- solara.Button("Execute", on_click=execute, disabled=not enable_execute)
69
- if result.error:
70
- solara.Error(f"Ooops {result.error}")
71
- elif not query:
72
- solara.Info("No query")
73
-
74
- elif result.value is not None:
75
- solara.Markdown(f"Result for query: `{query_executed}`")
76
- df = result.value
77
- solara.DataFrame(df)
78
- elif query_executed is not None:
79
- with solara.Div():
80
- solara.Text("Loading data...")
81
- rv.ProgressCircular(indeterminate=True, class_="solara-progress")
82
- return main
58
+
59
+ solara.SqlCode(query=query, tables=table_hints, on_query=set_query)
60
+ enable_execute = (query != query_executed) or result.error is not None
61
+
62
+ def execute():
63
+ set_query_executed(query)
64
+ if query == query_executed and result.error:
65
+ result.retry() # type: ignore
66
+
67
+ solara.Button("Execute", on_click=execute, disabled=not enable_execute)
68
+ if result.error:
69
+ solara.Error(f"Ooops {result.error}")
70
+ elif not query:
71
+ solara.Info("No query")
72
+
73
+ elif result.value is not None:
74
+ solara.Markdown(f"Result for query: `{query_executed}`")
75
+ df = result.value
76
+ solara.DataFrame(df)
77
+ elif query_executed is not None:
78
+ with solara.Div():
79
+ solara.Text("Loading data...")
80
+ solara.v.ProgressCircular(indeterminate=True, class_="solara-progress")
83
81
 
84
82
 
85
83
  __doc__ += apidoc(solara.SqlCode.f) # type: ignore
@@ -8,20 +8,18 @@ from solara.website.utils import apidoc
8
8
 
9
9
  @solara.component
10
10
  def Page():
11
- title, set_title = solara.use_state(cast(Optional[str], "Custom title!"))
12
- with solara.VBox() as main:
13
- solara.ToggleButtonsSingle(value=title, values=[None, "Custom title!", "Different custom title"], on_value=set_title)
14
-
15
- if title is not None:
16
- # if the title is not set in a child component, the parent's title will be used
17
- with solara.Head():
18
- # title should always occur inside a Head component
19
- solara.Title(title)
20
- solara.Info(f"Your browser tab title should say {title}", classes=["mt-4"])
21
- else:
22
- solara.Warning("If no title is set, the parent title is used.", classes=["mt-4"])
23
-
24
- return main
11
+ title = solara.use_reactive(cast(Optional[str], "Custom title!"))
12
+
13
+ solara.ToggleButtonsSingle(value=title, values=[None, "Custom title!", "Different custom title"])
14
+
15
+ if title is not None:
16
+ # if the title is not set in a child component, the parent's title will be used
17
+ with solara.Head():
18
+ # title should always occur inside a Head component
19
+ solara.Title(title)
20
+ solara.Info(f"Your browser tab title should say {title}", classes=["mt-4"])
21
+ else:
22
+ solara.Warning("If no title is set, the parent title is used.", classes=["mt-4"])
25
23
 
26
24
 
27
25
  __doc__ += apidoc(solara.Title.f) # type: ignore
@@ -17,24 +17,23 @@ from solara.website.utils import apidoc
17
17
 
18
18
  @solara.component
19
19
  def Page():
20
- icon, set_icon = solara.use_state(True)
21
- dense, set_dense = solara.use_state(False)
22
- outlined, set_outlined = solara.use_state(True)
23
- text, set_text = solara.use_state(True)
24
- with solara.VBox() as main:
25
- with solara.GridFixed(4):
26
- solara.Checkbox(label="Use icon", value=icon, on_value=set_icon)
27
- solara.Checkbox(label="Show dense", value=dense, on_value=set_dense)
28
- solara.Checkbox(label="Show as text", value=text, on_value=set_text)
29
- solara.Checkbox(label="Show outlined", value=outlined, on_value=set_outlined)
30
- solara.Error(
31
- f"This is solara.Error(label='...', text={text}, dense={dense}, outlined={outlined}, icon={icon})",
32
- text=text,
33
- dense=dense,
34
- outlined=outlined,
35
- icon=icon,
36
- )
37
- return main
20
+ icon = solara.use_reactive(True)
21
+ dense = solara.use_reactive(False)
22
+ outlined = solara.use_reactive(True)
23
+ text = solara.use_reactive(True)
24
+
25
+ with solara.GridFixed(4):
26
+ solara.Checkbox(label="Use icon", value=icon)
27
+ solara.Checkbox(label="Show dense", value=dense)
28
+ solara.Checkbox(label="Show as text", value=text)
29
+ solara.Checkbox(label="Show outlined", value=outlined)
30
+ solara.Error(
31
+ f"This is solara.Error(label='...', text={text.value}, dense={dense.value}, outlined={outlined.value}, icon={icon.value})",
32
+ text=text.value,
33
+ dense=dense.value,
34
+ outlined=outlined.value,
35
+ icon=icon.value,
36
+ )
38
37
 
39
38
 
40
39
  __doc__ += apidoc(solara.Error.f) # type: ignore
@@ -17,24 +17,23 @@ from solara.website.utils import apidoc
17
17
 
18
18
  @solara.component
19
19
  def Page():
20
- icon, set_icon = solara.use_state(True)
21
- dense, set_dense = solara.use_state(False)
22
- outlined, set_outlined = solara.use_state(True)
23
- text, set_text = solara.use_state(True)
24
- with solara.VBox() as main:
25
- with solara.GridFixed(4):
26
- solara.Checkbox(label="Use icon", value=icon, on_value=set_icon)
27
- solara.Checkbox(label="Show dense", value=dense, on_value=set_dense)
28
- solara.Checkbox(label="Show as text", value=text, on_value=set_text)
29
- solara.Checkbox(label="Show outlined", value=outlined, on_value=set_outlined)
30
- solara.Info(
31
- f"This is solara.Info(label='...', text={text}, dense={dense}, outlined={outlined}, icon={icon})",
32
- text=text,
33
- dense=dense,
34
- outlined=outlined,
35
- icon=icon,
36
- )
37
- return main
20
+ icon = solara.use_reactive(True)
21
+ dense = solara.use_reactive(False)
22
+ outlined = solara.use_reactive(True)
23
+ text = solara.use_reactive(True)
24
+
25
+ with solara.GridFixed(4):
26
+ solara.Checkbox(label="Use icon", value=icon)
27
+ solara.Checkbox(label="Show dense", value=dense)
28
+ solara.Checkbox(label="Show as text", value=text)
29
+ solara.Checkbox(label="Show outlined", value=outlined)
30
+ solara.Info(
31
+ f"This is solara.Info(label='...', text={text.value}, dense={dense.value}, outlined={outlined.value}, icon={icon.value})",
32
+ text=text.value,
33
+ dense=dense.value,
34
+ outlined=outlined.value,
35
+ icon=icon.value,
36
+ )
38
37
 
39
38
 
40
39
  __doc__ += apidoc(solara.Info.f) # type: ignore
@@ -5,6 +5,7 @@ title = "Examples"
5
5
 
6
6
  pycafe_projects = [
7
7
  "chatbot",
8
+ "tokenizer",
8
9
  ]
9
10
 
10
11
 
@@ -108,4 +108,6 @@ def Page():
108
108
  if promt_ai.pending:
109
109
  solara.Text("I'm thinking...", style={"font-size": "1rem", "padding-left": "20px"})
110
110
  solara.ProgressLinear()
111
- solara.lab.ChatInput(send_callback=promt_ai, disabled=promt_ai.pending)
111
+ # if we don't call .key(..) with a unique key, the ChatInput component will be re-created
112
+ # and we'll lose what we typed.
113
+ solara.lab.ChatInput(send_callback=promt_ai, disabled_send=promt_ai.pending, autofocus=True).key("input")
@@ -1,39 +1,32 @@
1
- from time import sleep
2
-
3
- import numpy as np
4
- from matplotlib import pyplot as plt
5
-
1
+ from typing import cast, Optional
2
+ import httpx
3
+ import asyncio
6
4
  import solara
5
+ import solara.lab
7
6
 
8
7
 
9
8
  @solara.component
10
9
  def Page():
11
- # define some state which will be updated regularly in a separate thread
12
- counter = solara.use_reactive(0)
10
+ btc = solara.use_reactive(cast(Optional[float], None))
13
11
 
14
- def render():
15
- """Infinite loop regularly mutating counter state"""
12
+ async def fetch_btc_price():
16
13
  while True:
17
- sleep(0.2)
18
- counter.value += 1
19
-
20
- # run the render loop in a separate thread
21
- result: solara.Result[bool] = solara.use_thread(render)
22
- if result.error:
23
- raise result.error
24
-
25
- # create the LiveUpdatingComponent, this component depends on the counter
26
- # value so will be redrawn whenever counter value changes
27
- LiveUpdatingComponent(counter.value)
28
-
29
-
30
- @solara.component
31
- def LiveUpdatingComponent(counter):
32
- """Component which will be redrawn whenever the counter value changes."""
33
- fig, ax = plt.subplots()
34
- ax.plot(np.arange(10), np.random.random(10))
35
- plt.close(fig)
36
- solara.FigureMatplotlib(fig)
14
+ await asyncio.sleep(1)
15
+ async with httpx.AsyncClient() as client:
16
+ url = "https://api.binance.com/api/v1/ticker/price?symbol=BTCUSDT"
17
+ response = await client.get(url)
18
+ btc.value = float(response.json()["price"])
19
+ print("btc.value", btc.value)
20
+
21
+ fetch_result = solara.lab.use_task(fetch_btc_price, dependencies=[])
22
+ # the task keeps running, so is always in the pending mode, so we combine it with the btc value being None
23
+ if fetch_result.pending and btc.value is None:
24
+ solara.Text("Fetching BTC price...")
25
+ else:
26
+ if fetch_result.error:
27
+ solara.Error(f"Error fetching BTC price: {fetch_result.exception}")
28
+ else:
29
+ solara.Text(f"BTC price: ${btc.value}")
37
30
 
38
31
 
39
32
  Page()
@@ -40,7 +40,7 @@ def Page():
40
40
  for pokemon in pokemons[:20]:
41
41
  with solara.Div():
42
42
  name = pokemon["name"]
43
- url = f'{pokemon_base_url}/{pokemon["image"]}'
43
+ url = f"{pokemon_base_url}/{pokemon['image']}"
44
44
  # TODO: how to do this with solara
45
45
  rv.Img(src=url, contain=True, max_height="200px")
46
46
  solara.Text(name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solara-ui
3
- Version: 1.43.0
3
+ Version: 1.44.0
4
4
  Dynamic: Summary
5
5
  Project-URL: Home, https://www.github.com/widgetti/solara
6
6
  Project-URL: Documentation, https://solara.dev