solara-ui 1.42.0__py2.py3-none-any.whl → 1.43.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.
- solara/__init__.py +1 -1
- solara/__main__.py +10 -5
- solara/_stores.py +14 -10
- solara/components/__init__.py +18 -1
- solara/components/datatable.py +4 -4
- solara/components/input.py +5 -1
- solara/components/markdown.py +46 -10
- solara/components/misc.py +2 -2
- solara/lab/components/__init__.py +1 -0
- solara/lab/components/chat.py +3 -3
- solara/lab/components/input_time.py +133 -0
- solara/lab/hooks/dataframe.py +1 -0
- solara/lab/utils/dataframe.py +11 -1
- solara/server/app.py +63 -30
- solara/server/flask.py +12 -2
- solara/server/jupyter/server_extension.py +1 -0
- solara/server/kernel.py +50 -3
- solara/server/kernel_context.py +66 -7
- solara/server/patch.py +25 -29
- solara/server/server.py +15 -5
- solara/server/settings.py +11 -0
- solara/server/shell.py +19 -1
- solara/server/starlette.py +37 -9
- solara/server/static/solara_bootstrap.py +1 -1
- solara/settings.py +3 -0
- solara/tasks.py +18 -8
- solara/test/pytest_plugin.py +1 -0
- solara/toestand.py +33 -2
- solara/util.py +18 -0
- solara/website/components/docs.py +4 -0
- solara/website/components/markdown.py +17 -3
- solara/website/pages/changelog/changelog.md +9 -1
- solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +10 -0
- solara/website/pages/documentation/api/routing/route.py +10 -12
- solara/website/pages/documentation/api/routing/use_route.py +26 -30
- solara/website/pages/documentation/components/advanced/link.py +6 -8
- solara/website/pages/documentation/components/advanced/meta.py +6 -9
- solara/website/pages/documentation/components/advanced/style.py +7 -9
- solara/website/pages/documentation/components/input/file_browser.py +12 -14
- solara/website/pages/documentation/components/lab/input_time.py +15 -0
- solara/website/pages/documentation/components/layout/columns_responsive.py +37 -39
- solara/website/pages/documentation/components/layout/gridfixed.py +4 -6
- solara/website/pages/documentation/components/output/html.py +1 -3
- solara/website/pages/documentation/components/page/head.py +4 -7
- solara/website/pages/documentation/examples/__init__.py +9 -0
- solara/website/pages/documentation/examples/ai/chatbot.py +60 -44
- solara/website/pages/documentation/examples/general/live_update.py +1 -0
- solara/website/pages/documentation/faq/content/99-faq.md +9 -0
- solara/website/pages/documentation/getting_started/content/00-quickstart.md +1 -1
- solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +23 -19
- solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +2 -2
- solara/website/pages/roadmap/roadmap.md +3 -0
- {solara_ui-1.42.0.dist-info → solara_ui-1.43.0.dist-info}/METADATA +2 -2
- {solara_ui-1.42.0.dist-info → solara_ui-1.43.0.dist-info}/RECORD +58 -56
- {solara_ui-1.42.0.data → solara_ui-1.43.0.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
- {solara_ui-1.42.0.data → solara_ui-1.43.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
- {solara_ui-1.42.0.dist-info → solara_ui-1.43.0.dist-info}/WHEEL +0 -0
- {solara_ui-1.42.0.dist-info → solara_ui-1.43.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -23,46 +23,44 @@ columns_xlarge = solara.reactive(1)
|
|
|
23
23
|
|
|
24
24
|
@solara.component
|
|
25
25
|
def Page():
|
|
26
|
-
with solara.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
solara.IntSlider("Children", max=20).connect(children_count)
|
|
26
|
+
with solara.Card("Controls"):
|
|
27
|
+
solara.Checkbox(label="Wrap").connect(wrap)
|
|
28
|
+
solara.Checkbox(label="Gutters").connect(gutters)
|
|
29
|
+
solara.Checkbox(label="Dense gutters").connect(gutters_dense)
|
|
30
|
+
solara.IntSlider("Children", max=20).connect(children_count)
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return main
|
|
32
|
+
solara.Select("columns default", values=[1, 2, 3, 4, 6, 12]).connect(columns_default) # type: ignore
|
|
33
|
+
solara.Select("columns small", values=[1, 2, 3, 4, 6, 12]).connect(columns_small) # type: ignore
|
|
34
|
+
solara.Select("columns medium", values=[1, 2, 3, 4, 6, 12]).connect(columns_medium) # type: ignore
|
|
35
|
+
solara.Select("columns large", values=[1, 2, 3, 4, 6, 12]).connect(columns_large) # type: ignore
|
|
36
|
+
solara.Select("columns xlarge", values=[1, 2, 3, 4, 6, 12]).connect(columns_xlarge) # type: ignore
|
|
37
|
+
# taken from https://v2.vuetifyjs.com/en/styles/display/#display
|
|
38
|
+
solara.HTML(
|
|
39
|
+
"h2", unsafe_innerHTML=f"Current screensize is xsmall/default, each child is {columns_default.value} points wide", class_="ma-2 d-flex d-sm-none ma"
|
|
40
|
+
)
|
|
41
|
+
solara.HTML(
|
|
42
|
+
"h2", unsafe_innerHTML=f"Current screensize is small, each child is {columns_small.value} points wide", class_="ma-2 d-none d-sm-flex d-md-none"
|
|
43
|
+
)
|
|
44
|
+
solara.HTML(
|
|
45
|
+
"h2", unsafe_innerHTML=f"Current screensize is medium, each child is {columns_medium.value} points wide", class_="ma-2 d-none d-md-flex d-lg-none"
|
|
46
|
+
)
|
|
47
|
+
solara.HTML(
|
|
48
|
+
"h2", unsafe_innerHTML=f"Current screensize is large, each child is {columns_large.value} points wide", class_="ma-2 d-none d-lg-flex d-xl-none"
|
|
49
|
+
)
|
|
50
|
+
solara.HTML("h2", unsafe_innerHTML=f"Current screensize is xlarge, each child is {columns_xlarge.value} points wide", class_="ma-2 d-none d-xl-flex")
|
|
51
|
+
solara.Markdown("Change the screen size to see the effect of the different columns sizes.")
|
|
52
|
+
with solara.ColumnsResponsive(
|
|
53
|
+
default=columns_default.value,
|
|
54
|
+
small=columns_small.value,
|
|
55
|
+
medium=columns_medium.value,
|
|
56
|
+
large=columns_large.value,
|
|
57
|
+
xlarge=columns_xlarge.value,
|
|
58
|
+
wrap=wrap.value,
|
|
59
|
+
gutters=gutters.value,
|
|
60
|
+
gutters_dense=gutters_dense.value,
|
|
61
|
+
):
|
|
62
|
+
for i in range(children_count.value):
|
|
63
|
+
solara.Text(f"{i}")
|
|
66
64
|
|
|
67
65
|
|
|
68
66
|
__doc__ += apidoc(solara.ColumnsResponsive.f) # type: ignore
|
|
@@ -13,9 +13,7 @@ title = "GridFixed"
|
|
|
13
13
|
|
|
14
14
|
@solara.component
|
|
15
15
|
def Page():
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
ColorCard(color, color)
|
|
21
|
-
return main
|
|
16
|
+
colors = "green red orange brown yellow pink".split()
|
|
17
|
+
with solara.GridFixed(columns=3):
|
|
18
|
+
for color in colors:
|
|
19
|
+
ColorCard(color, color)
|
|
@@ -6,13 +6,10 @@ from solara.website.utils import apidoc
|
|
|
6
6
|
|
|
7
7
|
@solara.component
|
|
8
8
|
def Page():
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
solara.Title("Custom title")
|
|
14
|
-
|
|
15
|
-
return main
|
|
9
|
+
solara.Info("A Head component does not render somesome visual on the page, but it is used to avoid duplicate tags, such as titles.")
|
|
10
|
+
with solara.Head():
|
|
11
|
+
# title should always occur inside a Head component
|
|
12
|
+
solara.Title("Custom title")
|
|
16
13
|
|
|
17
14
|
|
|
18
15
|
__doc__ += apidoc(solara.Head.f) # type: ignore
|
|
@@ -3,6 +3,10 @@ from solara.website.components import Gallery, MarkdownWithMetadata
|
|
|
3
3
|
|
|
4
4
|
title = "Examples"
|
|
5
5
|
|
|
6
|
+
pycafe_projects = [
|
|
7
|
+
"chatbot",
|
|
8
|
+
]
|
|
9
|
+
|
|
6
10
|
|
|
7
11
|
@solara.component
|
|
8
12
|
def Page(route_external=None):
|
|
@@ -37,6 +41,11 @@ def Layout(children):
|
|
|
37
41
|
with solara.Column(style={"max-width": "min(100%, 1024px)", "width": "100%"}):
|
|
38
42
|
if route_current.path != "/":
|
|
39
43
|
solara.Button("View source code on GitHub", icon_name="mdi-github-circle", href=github_url, class_="ma-2", target="_blank", text=True)
|
|
44
|
+
if route_current.path in pycafe_projects:
|
|
45
|
+
pycafe_url = f"https://py.cafe/solara/{route_current.path}"
|
|
46
|
+
solara.Button(
|
|
47
|
+
"Run this example on PyCafe", icon_name="mdi-coffee-to-go-outline", href=pycafe_url, class_="ma-2", target="_blank", text=True
|
|
48
|
+
)
|
|
40
49
|
if not hasattr(module, "Page"):
|
|
41
50
|
solara.Error(f"No Page component found in {module}")
|
|
42
51
|
else:
|
|
@@ -5,9 +5,10 @@ A way to create a chatbot using OpenAI's GPT-4 API, utilizing their new API, and
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
-
from typing import List
|
|
8
|
+
from typing import List, cast
|
|
9
9
|
|
|
10
|
-
from openai import
|
|
10
|
+
from openai import AsyncOpenAI
|
|
11
|
+
from openai.types.chat import ChatCompletionMessageParam
|
|
11
12
|
from typing_extensions import TypedDict
|
|
12
13
|
|
|
13
14
|
import solara
|
|
@@ -15,18 +16,33 @@ import solara.lab
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class MessageDict(TypedDict):
|
|
18
|
-
role: str
|
|
19
|
+
role: str # "user" or "assistant"
|
|
19
20
|
content: str
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
if os.getenv("OPENAI_API_KEY") is None and "OPENAI_API_KEY" not in os.environ:
|
|
23
|
-
openai = None
|
|
24
|
-
else:
|
|
25
|
-
openai = OpenAI()
|
|
26
|
-
openai.api_key = os.getenv("OPENAI_API_KEY") # type: ignore
|
|
27
|
-
|
|
28
23
|
messages: solara.Reactive[List[MessageDict]] = solara.reactive([])
|
|
29
24
|
|
|
25
|
+
try:
|
|
26
|
+
import pycafe
|
|
27
|
+
|
|
28
|
+
OPENAI_API_KEY = pycafe.get_secret(
|
|
29
|
+
"OPENAI_API_KEY",
|
|
30
|
+
"""We need an OpenAI API key to generate text.
|
|
31
|
+
|
|
32
|
+
Go to [OpenAI](https://platform.openai.com/account/api-keys) to get one.
|
|
33
|
+
|
|
34
|
+
Or read [this](https://www.rebelmouse.com/openai-account-set-up) article for
|
|
35
|
+
more information.
|
|
36
|
+
|
|
37
|
+
Or read more [about secrets on PyCafe](/docs/secrets)
|
|
38
|
+
|
|
39
|
+
""",
|
|
40
|
+
)
|
|
41
|
+
except ModuleNotFoundError:
|
|
42
|
+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
|
43
|
+
|
|
44
|
+
openai = AsyncOpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
|
|
45
|
+
|
|
30
46
|
|
|
31
47
|
def no_api_key_message():
|
|
32
48
|
messages.value = [
|
|
@@ -37,45 +53,44 @@ def no_api_key_message():
|
|
|
37
53
|
]
|
|
38
54
|
|
|
39
55
|
|
|
40
|
-
|
|
56
|
+
@solara.lab.task
|
|
57
|
+
async def promt_ai(message: str):
|
|
58
|
+
if openai is None:
|
|
59
|
+
no_api_key_message()
|
|
60
|
+
return
|
|
61
|
+
|
|
41
62
|
messages.value = [
|
|
42
|
-
*messages.value
|
|
43
|
-
{
|
|
44
|
-
"role": "assistant",
|
|
45
|
-
"content": messages.value[-1]["content"] + chunk,
|
|
46
|
-
},
|
|
63
|
+
*messages.value,
|
|
64
|
+
{"role": "user", "content": message},
|
|
47
65
|
]
|
|
66
|
+
# The part below can be replaced with a call to your own
|
|
67
|
+
response = await openai.chat.completions.create(
|
|
68
|
+
model="gpt-4-1106-preview",
|
|
69
|
+
# our MessageDict is compatible with the OpenAI types
|
|
70
|
+
messages=cast(List[ChatCompletionMessageParam], messages.value),
|
|
71
|
+
stream=True,
|
|
72
|
+
)
|
|
73
|
+
# start with an empty reply message, so we render and empty message in the chat
|
|
74
|
+
# while the AI is thinking
|
|
75
|
+
messages.value = [*messages.value, {"role": "assistant", "content": ""}]
|
|
76
|
+
# and update it with the response
|
|
77
|
+
async for chunk in response:
|
|
78
|
+
if chunk.choices[0].finish_reason == "stop": # type: ignore
|
|
79
|
+
return
|
|
80
|
+
# replace the last message element with the appended content
|
|
81
|
+
delta = chunk.choices[0].delta.content
|
|
82
|
+
assert delta is not None
|
|
83
|
+
updated_message: MessageDict = {
|
|
84
|
+
"role": "assistant",
|
|
85
|
+
"content": messages.value[-1]["content"] + delta,
|
|
86
|
+
}
|
|
87
|
+
# replace the last message element with the appended content
|
|
88
|
+
# which will update the UI
|
|
89
|
+
messages.value = [*messages.value[:-1], updated_message]
|
|
48
90
|
|
|
49
91
|
|
|
50
92
|
@solara.component
|
|
51
93
|
def Page():
|
|
52
|
-
user_message_count = len([m for m in messages.value if m["role"] == "user"])
|
|
53
|
-
|
|
54
|
-
def send(message):
|
|
55
|
-
messages.value = [
|
|
56
|
-
*messages.value,
|
|
57
|
-
{"role": "user", "content": message},
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
def call_openai():
|
|
61
|
-
if user_message_count == 0:
|
|
62
|
-
return
|
|
63
|
-
if openai is None:
|
|
64
|
-
no_api_key_message()
|
|
65
|
-
return
|
|
66
|
-
response = openai.chat.completions.create(
|
|
67
|
-
model="gpt-4-1106-preview",
|
|
68
|
-
messages=messages.value, # type: ignore
|
|
69
|
-
stream=True,
|
|
70
|
-
)
|
|
71
|
-
messages.value = [*messages.value, {"role": "assistant", "content": ""}]
|
|
72
|
-
for chunk in response:
|
|
73
|
-
if chunk.choices[0].finish_reason == "stop": # type: ignore
|
|
74
|
-
return
|
|
75
|
-
add_chunk_to_ai_message(chunk.choices[0].delta.content) # type: ignore
|
|
76
|
-
|
|
77
|
-
task = solara.lab.use_task(call_openai, dependencies=[user_message_count]) # type: ignore
|
|
78
|
-
|
|
79
94
|
with solara.Column(
|
|
80
95
|
style={"width": "100%", "height": "50vh"},
|
|
81
96
|
):
|
|
@@ -90,6 +105,7 @@ def Page():
|
|
|
90
105
|
border_radius="20px",
|
|
91
106
|
):
|
|
92
107
|
solara.Markdown(item["content"])
|
|
93
|
-
if
|
|
108
|
+
if promt_ai.pending:
|
|
94
109
|
solara.Text("I'm thinking...", style={"font-size": "1rem", "padding-left": "20px"})
|
|
95
|
-
|
|
110
|
+
solara.ProgressLinear()
|
|
111
|
+
solara.lab.ChatInput(send_callback=promt_ai, disabled=promt_ai.pending)
|
|
@@ -101,3 +101,12 @@ If you upgrade from an older version to 1.30 or later, the order in which pip in
|
|
|
101
101
|
```bash
|
|
102
102
|
$ pip install solara-server --force-reinstall
|
|
103
103
|
```
|
|
104
|
+
|
|
105
|
+
# I see an error in the browser console
|
|
106
|
+
|
|
107
|
+
If you see an error like this in the browser console:
|
|
108
|
+
```
|
|
109
|
+
Uncaught Error: Script error for "base/js/namespace", needed by: /jupyter/nbextensions/dash/main.js
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
See [ignoring notebook extensions](/documentation/advanced/understanding/solara-server#ignoring-notebook-extensions) for more information.
|
|
@@ -94,7 +94,7 @@ Or the more modern Jupyter lab:
|
|
|
94
94
|
You can also run the script as a standalone app. This requires the extra packages `qtpy` and `PySide6` (or `PyQt6`) to be installed.
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
|
-
$ pip install
|
|
97
|
+
$ pip install qtpy PySide6
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
Run from the command line in the same directory where you put your file (`sol.py`):
|
solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb
CHANGED
|
@@ -7,17 +7,19 @@
|
|
|
7
7
|
"source": [
|
|
8
8
|
"# Build your Jupyter dashboard using Solara\n",
|
|
9
9
|
"\n",
|
|
10
|
-
"Welcome to the first part of a series of tutorials
|
|
10
|
+
"Welcome to the first part of a series of tutorials showing you how to create a dashboard in Jupyter and deploy it as a standalone web app. Importantly, you won't need to rewrite your app in a different framework for deployment. We will use a pure Python solution with no JavaScript or CSS required.\n",
|
|
11
11
|
"\n",
|
|
12
|
-
"Jupyter notebooks are an incredible
|
|
13
|
-
"
|
|
12
|
+
"Jupyter notebooks are an incredible data analysis tool since they blend code, visualization, and narrative into a single document. However, we do not want to show the code if the insights must be presented to a non-technical audience.\n",
|
|
13
|
+
"\n",
|
|
14
|
+
"\n",
|
|
15
|
+
"Built on top of ipywidgets, the Solara framework integrates well into the Jupyter notebook, Jupyter lab as well as other Jupyter environments, and as we will see in a later article, can be deployed efficiently using the Solara server. This, by itself, makes Solara a perfect solution for creating dashboards or data apps.\n",
|
|
14
16
|
"\n",
|
|
15
17
|
"In this tutorial, we will create a simple dashboard using Solara's UI components. The final product will allow an end-user to filter,\n",
|
|
16
18
|
"visualize and explore a dataset on a map.\n",
|
|
17
19
|
"\n",
|
|
18
20
|
"\n",
|
|
19
21
|
"\n",
|
|
20
|
-
"## Pre-
|
|
22
|
+
"## Pre-requisites \n",
|
|
21
23
|
"\n",
|
|
22
24
|
"You need to install `pandas`, `matplotlib`, `folium` and `solara`. Assuming you are using pip, you can execute on your shell:\n",
|
|
23
25
|
"\n",
|
|
@@ -43,7 +45,7 @@
|
|
|
43
45
|
"id": "6cc6256a",
|
|
44
46
|
"metadata": {},
|
|
45
47
|
"source": [
|
|
46
|
-
"The first thing we do when we read in the data is to print it out
|
|
48
|
+
"The first thing we do when we read in the data is to print it out to see what the dataset contains."
|
|
47
49
|
]
|
|
48
50
|
},
|
|
49
51
|
{
|
|
@@ -365,7 +367,7 @@
|
|
|
365
367
|
"id": "08a9644a",
|
|
366
368
|
"metadata": {},
|
|
367
369
|
"source": [
|
|
368
|
-
"The data looks clean but since we will work with the `Category` and `PdDistrict` column data,
|
|
370
|
+
"The data looks clean, but since we will work with the `Category` and `PdDistrict` column data, let us convert those columns to title case."
|
|
369
371
|
]
|
|
370
372
|
},
|
|
371
373
|
{
|
|
@@ -700,7 +702,7 @@
|
|
|
700
702
|
"id": "b0e37cb4",
|
|
701
703
|
"metadata": {},
|
|
702
704
|
"source": [
|
|
703
|
-
"Now, with our filtered dataset, we create two
|
|
705
|
+
"Now, with our filtered dataset, we create two bar charts. We use regular Pandas and Matplotlib, but Seaborn or Plotly would also be appropriate choices."
|
|
704
706
|
]
|
|
705
707
|
},
|
|
706
708
|
{
|
|
@@ -750,9 +752,9 @@
|
|
|
750
752
|
"id": "0e71ff2f",
|
|
751
753
|
"metadata": {},
|
|
752
754
|
"source": [
|
|
753
|
-
"Since we do not need
|
|
755
|
+
"Since we do not need bidirectional communication (e.g., we do not need to receive events or data from our map), we use Folium to display the locations of the committed crimes on a map. If we do need bidirectional communication, we can also decide to use [ipyleaflet](https://ipyleaflet.readthedocs.io/).\n",
|
|
754
756
|
"\n",
|
|
755
|
-
"
|
|
757
|
+
"Since we cannot display all the data on the map without crashing your browser, we limit it to a maximum of 50 points."
|
|
756
758
|
]
|
|
757
759
|
},
|
|
758
760
|
{
|
|
@@ -800,9 +802,9 @@
|
|
|
800
802
|
"source": [
|
|
801
803
|
"## Making our first reactive visualization\n",
|
|
802
804
|
"\n",
|
|
803
|
-
"The above code works nicely, but if we want to explore different types of crimes, we need to
|
|
805
|
+
"The above code works nicely, but if we want to explore different types of crimes, we need to modify and run all cells that determine our output manually. Would it not be much better to have a UI with controls determining the filtering and a view displaying the filtered data interactively?\n",
|
|
804
806
|
"\n",
|
|
805
|
-
"
|
|
807
|
+
"Let's start by importing the solara package and creating three reactive variables."
|
|
806
808
|
]
|
|
807
809
|
},
|
|
808
810
|
{
|
|
@@ -814,9 +816,7 @@
|
|
|
814
816
|
"source": [
|
|
815
817
|
"import solara\n",
|
|
816
818
|
"\n",
|
|
817
|
-
"districts = solara.reactive(\n",
|
|
818
|
-
" [\"Bayview\", \"Northern\"],\n",
|
|
819
|
-
")\n",
|
|
819
|
+
"districts = solara.reactive([\"Bayview\", \"Northern\"])\n",
|
|
820
820
|
"categories = solara.reactive([\"Vandalism\", \"Assault\", \"Robbery\"])\n",
|
|
821
821
|
"limit = solara.reactive(100)"
|
|
822
822
|
]
|
|
@@ -826,9 +826,9 @@
|
|
|
826
826
|
"id": "28622c20",
|
|
827
827
|
"metadata": {},
|
|
828
828
|
"source": [
|
|
829
|
-
"
|
|
829
|
+
"A reactive variable is a container around a value (like an int, string, or list) that allows the UI to listen to changes automatically. Any change to your_reactive_variable.value will be picked up by Solara components that use them so that they can automatically redraw or update themselves.\n",
|
|
830
830
|
"\n",
|
|
831
|
-
"
|
|
831
|
+
"Let us now create our first component (View), which filters the data based on the reactive variables and shows the map and the charts. Solara supports the display mechanism of Jupyter so that we can use our previously defined functions."
|
|
832
832
|
]
|
|
833
833
|
},
|
|
834
834
|
{
|
|
@@ -859,7 +859,9 @@
|
|
|
859
859
|
"id": "0b05c1db",
|
|
860
860
|
"metadata": {},
|
|
861
861
|
"source": [
|
|
862
|
-
"Note that some
|
|
862
|
+
"Note that some UI parts (like the warning and the charts) are conditional. Solara will automatically find out what to add, remove, or update without you having to do this manually. Solara is declarative (similar to ReactJS) but also reactive. If we change the reactive variables, Solara will see those changes and notify the component instances that use its value.\n",
|
|
863
|
+
"\n",
|
|
864
|
+
"If we run the next lines of code in our notebook, our View will automatically update."
|
|
863
865
|
]
|
|
864
866
|
},
|
|
865
867
|
{
|
|
@@ -878,7 +880,9 @@
|
|
|
878
880
|
"id": "8822d100",
|
|
879
881
|
"metadata": {},
|
|
880
882
|
"source": [
|
|
881
|
-
"We can now explore
|
|
883
|
+
"We can now explore our data much faster since we don't need to re-run the cells that depend on it.\n",
|
|
884
|
+
"\n",
|
|
885
|
+
"Solara's reactive and declarative nature makes it scalable to much larger applications than regular ipywidgets, where keeping the UI in sync and adding, removing, and updating widgets is a manual and bug-prone process."
|
|
882
886
|
]
|
|
883
887
|
},
|
|
884
888
|
{
|
|
@@ -888,7 +892,7 @@
|
|
|
888
892
|
"source": [
|
|
889
893
|
"## Adding controls\n",
|
|
890
894
|
"\n",
|
|
891
|
-
"We created a mini app in our notebook
|
|
895
|
+
"We created a declarative and reactive mini app in our notebook, but we still need to manually modify the values by executing a code cell in our Notebook. Now, let us create a UI to control it. All Solara input components support reactive variables. This means that controlling a reactive variable using a UI element is often a one-liner."
|
|
892
896
|
]
|
|
893
897
|
},
|
|
894
898
|
{
|
|
@@ -5,8 +5,8 @@ description: Solara is compatible with many different hosting solutions, such as
|
|
|
5
5
|
# Self hosted deployment
|
|
6
6
|
|
|
7
7
|
* [Flask](#flask)
|
|
8
|
-
* [Starlette](#
|
|
9
|
-
* [FastAPI](#
|
|
8
|
+
* [Starlette](#starlette)
|
|
9
|
+
* [FastAPI](#fastapi)
|
|
10
10
|
* [Voila](#voila)
|
|
11
11
|
* [Panel](#panel)
|
|
12
12
|
* [Nginx](#nginx)
|
|
@@ -13,6 +13,9 @@ Exciting news! We aim to release Solara 2.0 by the end of the year. For the 2.0
|
|
|
13
13
|
|
|
14
14
|
State mutation detection will be the default for Solara 2.0, but can be enabled in Solara > 1.41.0 by setting the environment variable `SOLARA_STORAGE_MUTATION_DETECTION=1`.
|
|
15
15
|
|
|
16
|
+
Using reactive variables in boolean comparisons will raise an error in Solara 2.0, but this can be used in Solara > 1.42.0 by setting the environment variable `SOLARA_ALLOW_REACTIVE_BOOLEAN=0`.
|
|
17
|
+
|
|
18
|
+
In Solara 2.0, `reacton.Fragment` will be used as the default container of any unwrapped sibling elements. This behaviour can be enabled in Solara > 1.42.0 by setting the environmental variable `SOLARA_DEFAULT_CONTAINER="Fragment"`.
|
|
16
19
|
|
|
17
20
|
- [See more details in the 2.0 milestone on GitHub.](https://github.com/widgetti/solara/milestone/1)
|
|
18
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: solara-ui
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.43.0
|
|
4
4
|
Dynamic: Summary
|
|
5
5
|
Project-URL: Home, https://www.github.com/widgetti/solara
|
|
6
6
|
Project-URL: Documentation, https://solara.dev
|
|
@@ -31,7 +31,7 @@ Requires-Dist: humanize
|
|
|
31
31
|
Requires-Dist: ipyvue>=1.9.0
|
|
32
32
|
Requires-Dist: ipyvuetify>=1.6.10
|
|
33
33
|
Requires-Dist: ipywidgets>=7.7
|
|
34
|
-
Requires-Dist: reacton>=1.
|
|
34
|
+
Requires-Dist: reacton>=1.9
|
|
35
35
|
Requires-Dist: requests
|
|
36
36
|
Provides-Extra: all
|
|
37
37
|
Requires-Dist: cachetools; extra == 'all'
|