solara-ui 1.43.0__py2.py3-none-any.whl → 1.44.1__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.
- solara/__init__.py +1 -1
- solara/__main__.py +2 -2
- solara/_stores.py +114 -6
- solara/cache.py +6 -4
- solara/checks.py +1 -1
- solara/components/select.py +1 -1
- solara/components/style.py +1 -1
- solara/hooks/use_reactive.py +16 -1
- solara/lab/components/chat.py +13 -7
- solara/server/app.py +4 -1
- solara/server/kernel_context.py +2 -2
- solara/server/patch.py +10 -1
- solara/server/server.py +1 -1
- solara/server/starlette.py +39 -9
- solara/server/static/solara_bootstrap.py +1 -1
- solara/tasks.py +12 -1
- solara/test/pytest_plugin.py +4 -3
- solara/toestand.py +86 -26
- solara/website/components/docs.py +20 -1
- solara/website/pages/changelog/changelog.md +32 -0
- solara/website/pages/documentation/advanced/content/10-howto/20-layout.md +1 -1
- solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +4 -2
- solara/website/pages/documentation/components/lab/theming.py +6 -4
- solara/website/pages/documentation/components/output/sql_code.py +23 -25
- solara/website/pages/documentation/components/page/title.py +12 -14
- solara/website/pages/documentation/components/status/error.py +17 -18
- solara/website/pages/documentation/components/status/info.py +17 -18
- solara/website/pages/documentation/examples/__init__.py +1 -0
- solara/website/pages/documentation/examples/ai/chatbot.py +3 -1
- solara/website/pages/documentation/examples/general/live_update.py +22 -29
- solara/website/pages/documentation/examples/general/pokemon_search.py +1 -1
- {solara_ui-1.43.0.dist-info → solara_ui-1.44.1.dist-info}/METADATA +1 -1
- {solara_ui-1.43.0.dist-info → solara_ui-1.44.1.dist-info}/RECORD +37 -37
- {solara_ui-1.43.0.data → solara_ui-1.44.1.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
- {solara_ui-1.43.0.data → solara_ui-1.44.1.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
- {solara_ui-1.43.0.dist-info → solara_ui-1.44.1.dist-info}/WHEEL +0 -0
- {solara_ui-1.43.0.dist-info → solara_ui-1.44.1.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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
178
|
-
for listener,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
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", "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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,37 @@
|
|
|
1
1
|
# Solara Changelog
|
|
2
2
|
|
|
3
|
+
## Version 1.44.0
|
|
4
|
+
|
|
5
|
+
* Feature: Separate `disable_send` and `disable_input` to allow typing but not sending or vice versa for the `ChatInput` component. [86fc2ad](https://github.com/widgetti/solara/commit/86fc2ad88a89ffe134eaa6700a8c416bcab036b4).
|
|
6
|
+
* Feature: Support `autofocus` for the `ChatInput` component. [31dad76](https://github.com/widgetti/solara/commit/31dad767c15e3671e610fcba5ae2b124bb0b5220).
|
|
7
|
+
* Feature: Allow websocket messages to be sent from main thread. [#953](https://github.com/widgetti/solara/pull/953).
|
|
8
|
+
* Bug Fix: Typing for `memoize`'s `key` was incorrect. [#959](https://github.com/widgetti/solara/pull/959).
|
|
9
|
+
* Bug Fix: Using `get_ipython` outside Solara's virtual kernels after importing Solara would return `None`. [#990](https://github.com/widgetti/solara/pull/990).
|
|
10
|
+
* Bug Fix: Solara lab components (and perhaps others) didn't work when used in Jupyter contexts. [#981](https://github.com/widgetti/solara/pull/981).
|
|
11
|
+
* Bug Fix: Threaded and async tasks behaving differently when cancelling. [5fe30dc](https://github.com/widgetti/solara/commit/5fe30dc27fe99ee056f3a046c0942386b181d00f).
|
|
12
|
+
* Bug Fix: Fixes to handling context for reactive variable subscription / event handling. [06f1670](https://github.com/widgetti/solara/commit/06f1670bc918231897d4e5dcb00374711fc83464), [4ff39c5](https://github.com/widgetti/solara/commit/4ff39c5647057db53788c925139a710ab045897b).
|
|
13
|
+
* Bug Fix: Running a Solara app under FastAPI / Starlette was broken. [133e2ca](https://github.com/widgetti/solara/commit/133e2cab13efe4277f98305cdba412c08b47936f).
|
|
14
|
+
* Bug Fix: Allow reactive variables to be set from outside kernel context. [#952](https://github.com/widgetti/solara/pull/952).
|
|
15
|
+
* Bug Fix: Do not ignore all errors on websocket disconnect. [1f6d98e](https://github.com/widgetti/solara/commit/1f6d98e2b5233116cc617cf7d9019d2748ba251d).
|
|
16
|
+
* Bug Fix: Enable `dense` option for SelectMultiple. [#939](https://github.com/widgetti/solara/pull/939).
|
|
17
|
+
|
|
18
|
+
## Version 1.43.0
|
|
19
|
+
* Feature: Time picker component. [#654](https://github.com/widgetti/solara/pull/654).
|
|
20
|
+
* 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).
|
|
21
|
+
* 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).
|
|
22
|
+
* Feature: Use index for row names of pandas dataframes. [#613](https://github.com/widgetti/solara/pull/613).
|
|
23
|
+
* Feature: Support setting `http_only` for Solara session cookie. [#876](https://github.com/widgetti/solara/pull/876).
|
|
24
|
+
* Feature: Allow disabling notebook extensions. [#842](https://github.com/widgetti/solara/pull/842).
|
|
25
|
+
* Feature: `custom_exceptions` is now defined in `FakeIPython`. [#839](https://github.com/widgetti/solara/pull/839).
|
|
26
|
+
* Bug Fix: `InputDate` would not accept values if format was changed. [#933](https://github.com/widgetti/solara/pull/933).
|
|
27
|
+
* Bug Fix: Close kernels when ASGI/Starlette server is shut down. [#930](https://github.com/widgetti/solara/pull/930).
|
|
28
|
+
* Bug Fix: Avoid and test for the existence of memory leaks. [#377](https://github.com/widgetti/solara/pull/377).
|
|
29
|
+
* Bug Fix: `send_text` sent bytes instead of string. [637a77f](https://github.com/widgetti/solara/commit/637a77f2539ee68555cf998313aee62cde802579).
|
|
30
|
+
* Bug Fix: Avoid solara run hanging because of PyPI version request. [#855](https://github.com/widgetti/solara/pull/855).
|
|
31
|
+
* Bug Fix: Catch exceptions raised by startlette on websocket send failure. [7e50ee7](https://github.com/widgetti/solara/commit/7e50ee7edb7a36644b02d9d80ae91e1ac292975e).
|
|
32
|
+
* Bug Fix: Numpy scalars were erroneously converted to a string instead of a number. [cffccca](https://github.com/widgetti/solara/commit/cffccca500c36e21357323168b73ddd716071885).
|
|
33
|
+
* Bug Fix: Failing websocket.send calls would suppress all errors. [51cbfa9](https://github.com/widgetti/solara/commit/51cbfa970d42e5ff2c2b25de268e951677013467).
|
|
34
|
+
|
|
3
35
|
## Version 1.42.0
|
|
4
36
|
* Feature: Mutation detection is now available under the `SOLARA_STORAGE_MUTATION_DETECTION` environmental variable. [#595](https://github.com/widgetti/solara/pull/595).
|
|
5
37
|
* 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
21
|
-
dense
|
|
22
|
-
outlined
|
|
23
|
-
text
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
21
|
-
dense
|
|
22
|
-
outlined
|
|
23
|
-
text
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
2
|
-
|
|
3
|
-
import
|
|
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
|
-
|
|
12
|
-
counter = solara.use_reactive(0)
|
|
10
|
+
btc = solara.use_reactive(cast(Optional[float], None))
|
|
13
11
|
|
|
14
|
-
def
|
|
15
|
-
"""Infinite loop regularly mutating counter state"""
|
|
12
|
+
async def fetch_btc_price():
|
|
16
13
|
while True:
|
|
17
|
-
sleep(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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)
|