solara-ui 1.41.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.
Files changed (83) hide show
  1. solara/__init__.py +1 -1
  2. solara/__main__.py +17 -6
  3. solara/_stores.py +189 -0
  4. solara/components/__init__.py +18 -1
  5. solara/components/component_vue.py +23 -0
  6. solara/components/datatable.py +4 -4
  7. solara/components/echarts.py +5 -2
  8. solara/components/echarts.vue +22 -5
  9. solara/components/file_drop.py +20 -0
  10. solara/components/input.py +21 -1
  11. solara/components/markdown.py +62 -17
  12. solara/components/misc.py +2 -2
  13. solara/components/spinner-solara.vue +2 -2
  14. solara/components/spinner.py +17 -2
  15. solara/hooks/use_reactive.py +8 -1
  16. solara/lab/components/__init__.py +1 -0
  17. solara/lab/components/chat.py +3 -3
  18. solara/lab/components/input_time.py +133 -0
  19. solara/lab/hooks/dataframe.py +1 -0
  20. solara/lab/utils/dataframe.py +11 -1
  21. solara/reactive.py +9 -3
  22. solara/server/app.py +63 -30
  23. solara/server/flask.py +12 -2
  24. solara/server/jupyter/server_extension.py +1 -0
  25. solara/server/kernel.py +52 -4
  26. solara/server/kernel_context.py +66 -7
  27. solara/server/patch.py +25 -29
  28. solara/server/qt.py +1 -1
  29. solara/server/server.py +15 -5
  30. solara/server/settings.py +11 -0
  31. solara/server/shell.py +19 -1
  32. solara/server/starlette.py +39 -11
  33. solara/server/static/solara_bootstrap.py +1 -1
  34. solara/settings.py +17 -0
  35. solara/tasks.py +18 -8
  36. solara/template/portal/pyproject.toml +1 -1
  37. solara/test/pytest_plugin.py +4 -0
  38. solara/toestand.py +170 -16
  39. solara/util.py +40 -0
  40. solara/website/components/docs.py +4 -0
  41. solara/website/components/markdown.py +60 -2
  42. solara/website/pages/changelog/changelog.md +17 -0
  43. solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +10 -0
  44. solara/website/pages/documentation/api/cross_filter/cross_filter_dataframe.py +4 -5
  45. solara/website/pages/documentation/api/cross_filter/cross_filter_report.py +3 -5
  46. solara/website/pages/documentation/api/cross_filter/cross_filter_select.py +3 -5
  47. solara/website/pages/documentation/api/cross_filter/cross_filter_slider.py +3 -5
  48. solara/website/pages/documentation/api/hooks/use_cross_filter.py +3 -5
  49. solara/website/pages/documentation/api/hooks/use_exception.py +9 -11
  50. solara/website/pages/documentation/api/hooks/use_previous.py +6 -9
  51. solara/website/pages/documentation/api/hooks/use_state_or_update.py +23 -26
  52. solara/website/pages/documentation/api/hooks/use_thread.py +11 -13
  53. solara/website/pages/documentation/api/routing/route.py +10 -12
  54. solara/website/pages/documentation/api/routing/use_route.py +26 -30
  55. solara/website/pages/documentation/api/utilities/on_kernel_start.py +17 -0
  56. solara/website/pages/documentation/components/advanced/link.py +6 -8
  57. solara/website/pages/documentation/components/advanced/meta.py +6 -9
  58. solara/website/pages/documentation/components/advanced/style.py +7 -9
  59. solara/website/pages/documentation/components/input/file_browser.py +12 -14
  60. solara/website/pages/documentation/components/input/input.py +22 -0
  61. solara/website/pages/documentation/components/lab/input_time.py +15 -0
  62. solara/website/pages/documentation/components/layout/columns_responsive.py +37 -39
  63. solara/website/pages/documentation/components/layout/gridfixed.py +4 -6
  64. solara/website/pages/documentation/components/output/html.py +1 -3
  65. solara/website/pages/documentation/components/page/head.py +4 -7
  66. solara/website/pages/documentation/components/viz/echarts.py +3 -1
  67. solara/website/pages/documentation/examples/__init__.py +9 -0
  68. solara/website/pages/documentation/examples/ai/chatbot.py +60 -44
  69. solara/website/pages/documentation/examples/general/live_update.py +1 -0
  70. solara/website/pages/documentation/examples/general/pokemon_search.py +3 -3
  71. solara/website/pages/documentation/examples/visualization/linked_views.py +0 -3
  72. solara/website/pages/documentation/faq/content/99-faq.md +9 -0
  73. solara/website/pages/documentation/getting_started/content/00-quickstart.md +2 -2
  74. solara/website/pages/documentation/getting_started/content/01-introduction.md +1 -1
  75. solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +23 -19
  76. solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +2 -2
  77. solara/website/pages/roadmap/roadmap.md +6 -0
  78. {solara_ui-1.41.0.dist-info → solara_ui-1.43.0.dist-info}/METADATA +9 -6
  79. {solara_ui-1.41.0.dist-info → solara_ui-1.43.0.dist-info}/RECORD +83 -80
  80. {solara_ui-1.41.0.dist-info → solara_ui-1.43.0.dist-info}/WHEEL +1 -1
  81. {solara_ui-1.41.0.data → solara_ui-1.43.0.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
  82. {solara_ui-1.41.0.data → solara_ui-1.43.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
  83. {solara_ui-1.41.0.dist-info → solara_ui-1.43.0.dist-info}/licenses/LICENSE +0 -0
@@ -19,3 +19,25 @@ __doc__ += "# InputFloat"
19
19
  __doc__ += apidoc(solara.InputFloat.f) # type: ignore
20
20
  __doc__ += "# InputInt"
21
21
  __doc__ += apidoc(solara.InputInt.f) # type: ignore
22
+
23
+ __doc__ += """
24
+ # Autofocus Example
25
+
26
+ ```solara
27
+ import solara
28
+ import solara.lab
29
+
30
+
31
+ @solara.component
32
+ def Page():
33
+ show_dialog = solara.use_reactive(False)
34
+ show_conditional = solara.use_reactive(False)
35
+ with solara.Row():
36
+ solara.Button("Show dialog", on_click=lambda: show_dialog.set(True))
37
+ solara.Button("Show conditionally rendered element", on_click=lambda: show_conditional.set(not show_conditional.value))
38
+ with solara.lab.ConfirmationDialog(open=show_dialog):
39
+ solara.InputFloat("Float here", autofocus=True)
40
+ if show_conditional.value:
41
+ solara.InputFloat("Float here", autofocus=True)
42
+ ```
43
+ """
@@ -0,0 +1,15 @@
1
+ """
2
+ # InputTime
3
+ """
4
+
5
+ import solara
6
+ from solara.website.components import NoPage
7
+ from solara.website.utils import apidoc
8
+
9
+ title = "InputTime"
10
+
11
+
12
+ __doc__ += apidoc(solara.lab.components.input_time.InputTime.f) # type: ignore
13
+
14
+
15
+ Page = NoPage
@@ -23,46 +23,44 @@ columns_xlarge = solara.reactive(1)
23
23
 
24
24
  @solara.component
25
25
  def Page():
26
- with solara.VBox() as main:
27
- with solara.Card("Controls"):
28
- solara.Checkbox(label="Wrap").connect(wrap)
29
- solara.Checkbox(label="Gutters").connect(gutters)
30
- solara.Checkbox(label="Dense gutters").connect(gutters_dense)
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
- solara.Select("columns default", values=[1, 2, 3, 4, 6, 12]).connect(columns_default) # type: ignore
34
- solara.Select("columns small", values=[1, 2, 3, 4, 6, 12]).connect(columns_small) # type: ignore
35
- solara.Select("columns medium", values=[1, 2, 3, 4, 6, 12]).connect(columns_medium) # type: ignore
36
- solara.Select("columns large", values=[1, 2, 3, 4, 6, 12]).connect(columns_large) # type: ignore
37
- solara.Select("columns xlarge", values=[1, 2, 3, 4, 6, 12]).connect(columns_xlarge) # type: ignore
38
- # taken from https://v2.vuetifyjs.com/en/styles/display/#display
39
- solara.HTML(
40
- "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"
41
- )
42
- solara.HTML(
43
- "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"
44
- )
45
- solara.HTML(
46
- "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"
47
- )
48
- solara.HTML(
49
- "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"
50
- )
51
- 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")
52
- solara.Markdown("Change the screen size to see the effect of the different columns sizes.")
53
- with solara.ColumnsResponsive(
54
- default=columns_default.value,
55
- small=columns_small.value,
56
- medium=columns_medium.value,
57
- large=columns_large.value,
58
- xlarge=columns_xlarge.value,
59
- wrap=wrap.value,
60
- gutters=gutters.value,
61
- gutters_dense=gutters_dense.value,
62
- ):
63
- for i in range(children_count.value):
64
- solara.Text(f"{i}")
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
- with solara.VBox() as main:
17
- colors = "green red orange brown yellow pink".split()
18
- with solara.GridFixed(columns=3):
19
- for color in colors:
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)
@@ -13,9 +13,7 @@ def Page():
13
13
  <li>Item 2
14
14
  </ul>
15
15
  """
16
- with solara.VBox() as main:
17
- solara.HTML(tag="div", unsafe_innerHTML=html)
18
- return main
16
+ solara.HTML(tag="div", unsafe_innerHTML=html)
19
17
 
20
18
 
21
19
  __doc__ += apidoc(solara.HTML.f) # type: ignore
@@ -6,13 +6,10 @@ from solara.website.utils import apidoc
6
6
 
7
7
  @solara.component
8
8
  def Page():
9
- with solara.VBox() as main:
10
- solara.Info("A Head component does not render somesome visual on the page, but it is used to avoid duplicate tags, such as titles.")
11
- with solara.Head():
12
- # title should always occur inside a Head component
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
@@ -63,7 +63,9 @@ def Page():
63
63
  with solara.ToggleButtonsSingle("bars", on_value=set_option):
64
64
  solara.Button("bars")
65
65
  solara.Button("pie")
66
- solara.FigureEcharts(option=options[option], on_click=set_click_data, on_mouseover=set_mouseover_data, on_mouseout=set_mouseout_data)
66
+ solara.FigureEcharts(
67
+ option=options[option], on_click=set_click_data, on_mouseover=set_mouseover_data, on_mouseout=set_mouseout_data, responsive=True
68
+ )
67
69
  with solara.Card("Event data"):
68
70
  solara.Markdown(f"**Click data**: {click_data}")
69
71
  solara.Markdown(f"**Mouseover data**: {mouseover_data}")
@@ -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 OpenAI
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
- def add_chunk_to_ai_message(chunk: str):
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[:-1],
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 task.pending:
108
+ if promt_ai.pending:
94
109
  solara.Text("I'm thinking...", style={"font-size": "1rem", "padding-left": "20px"})
95
- solara.lab.ChatInput(send_callback=send, disabled=task.pending)
110
+ solara.ProgressLinear()
111
+ solara.lab.ChatInput(send_callback=promt_ai, disabled=promt_ai.pending)
@@ -32,6 +32,7 @@ def LiveUpdatingComponent(counter):
32
32
  """Component which will be redrawn whenever the counter value changes."""
33
33
  fig, ax = plt.subplots()
34
34
  ax.plot(np.arange(10), np.random.random(10))
35
+ plt.close(fig)
35
36
  solara.FigureMatplotlib(fig)
36
37
 
37
38
 
@@ -10,8 +10,8 @@ from solara import use_fetch
10
10
  from solara.alias import rv
11
11
 
12
12
  github_url = solara.util.github_url(__file__)
13
-
14
- json_url = "https://jherr-pokemon.s3.us-west-1.amazonaws.com/index.json"
13
+ pokemon_base_url = "https://raw.githubusercontent.com/jherr/pokemon/0722479d4153b1db0d0326956b08b37f44a95a5f"
14
+ json_url = f"{pokemon_base_url}/index.json"
15
15
 
16
16
 
17
17
  @solara.component
@@ -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 = "https://jherr-pokemon.s3.us-west-1.amazonaws.com/" + 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)
@@ -65,7 +65,6 @@ def Page():
65
65
  else:
66
66
  clicked_row = None
67
67
 
68
- with solara.Column() as main:
69
68
  with solara.Row(justify="center", style={"flex-wrap": "wrap"}):
70
69
  ClickScatter(df, "sepal_length", "sepal_width", "species", clicked_row, on_click=set_click_point)
71
70
  ClickScatter(df, "petal_length", "petal_width", "species", clicked_row, on_click=set_click_point)
@@ -80,5 +79,3 @@ def Page():
80
79
  )
81
80
  else:
82
81
  solara.Info("Click to select a point")
83
-
84
- return main
@@ -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.
@@ -24,7 +24,7 @@ Run `pip install solara`, or follow the [Installation instructions](/documentati
24
24
 
25
25
  Put the following Python snippet in a file (we suggest `sol.py`), or put it in a Jupyter notebook cell:
26
26
 
27
- ```solara
27
+ ```solara {pycafe-link}
28
28
  import solara
29
29
 
30
30
  # Declare reactive variables at the top level. Components using these variables
@@ -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 pip install qtpy PySide6
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`):
@@ -48,7 +48,7 @@ Follow the [installation instructions](/documentation/getting_started/installing
48
48
 
49
49
  Create a file `myapp.py`, or put the following code in the Jupyter notebook:
50
50
 
51
- ```solara
51
+ ```solara {pycafe-link}
52
52
  import solara
53
53
 
54
54
  clicks = solara.reactive(0)
@@ -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 that will show you how to create a dashboard in Jupyter and deploy it as a standalone web app. Importantly, there will be no need to rewrite your app in a different framework, no need to use a non-Python solution, and no need to use JavaScript or CSS.\n",
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 tool for data analysis, since they enable blending code, visualization and narrative into a single document.\n",
13
- "However, if the insights need to be presented to a non-technical audience, we usually do not want to show the code.\n",
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
  "![image](https://dxhl76zpt6fap.cloudfront.net/public/docs/tutorial/jupyter-dashboard1.webp)\n",
19
21
  "\n",
20
- "## Pre-requisits \n",
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, to see what the dataset contains."
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, lets convert those columns to title case."
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 barcharts. We use regular pandas and matplotlib, but seaborn or plotly would also have been appropriate choices."
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 bi-directional 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 bi-directional communication, we can also decide to use [ipyleaflet](https://ipyleaflet.readthedocs.io/en/latest/usage/index.html).\n",
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
- "We cannot display all the data on the map without crashing your browser, so we limit to a maximum of a 50 points."
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 manually modify and run all cells that determine out output. Would it not be much better to have a UI with controls that determine the filtering, and a view that displays the filtered data interactively?\n",
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
- "Lets start by importing the solara package, and create three reactive variables."
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
- " A reactive variable is a container around a value (like a int, string or list) that allows the UI to automatically listen to changes. Any change to `your_reactive_variable.value` will be picked up by solara component that use them, so that they can automatically redraw or update itself.\n",
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
- " We 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 we can simply use our previously defined functions."
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 of the code (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 sees that changes and notifies the component instances that use its value. After executing the next lines of code, our `View` will automatically update."
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 out data much faster, since we don't need to re-run the cells that depended on it. "
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 that is declarative *and* reactive, but we still need to manually modify the values by executing a code cell, while we promised a UI to control it. Luckily, all Solara input components supports reactive variables. This means that controlling a reactive variable using a UI element is often a one-liner."
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](#flask)
9
- * [FastAPI](#flask)
8
+ * [Starlette](#starlette)
9
+ * [FastAPI](#fastapi)
10
10
  * [Voila](#voila)
11
11
  * [Panel](#panel)
12
12
  * [Nginx](#nginx)
@@ -11,6 +11,12 @@ Exciting news! We aim to release Solara 2.0 by the end of the year. For the 2.0
11
11
 
12
12
  - Elimination of common mistakes, such as detecting state mutations and avoiding misuse of hooks (e.g., using hooks in loops).
13
13
 
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
+
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"`.
19
+
14
20
  - [See more details in the 2.0 milestone on GitHub.](https://github.com/widgetti/solara/milestone/1)
15
21
 
16
22
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solara-ui
3
- Version: 1.41.0
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
@@ -26,18 +26,21 @@ License: The MIT License (MIT)
26
26
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
27
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28
28
  THE SOFTWARE.
29
- License-File: LICENSE
30
29
  Classifier: License :: OSI Approved :: MIT License
31
30
  Requires-Dist: humanize
32
31
  Requires-Dist: ipyvue>=1.9.0
33
32
  Requires-Dist: ipyvuetify>=1.6.10
34
33
  Requires-Dist: ipywidgets>=7.7
35
- Requires-Dist: reacton>=1.7.1
34
+ Requires-Dist: reacton>=1.9
36
35
  Requires-Dist: requests
37
36
  Provides-Extra: all
38
- Requires-Dist: solara-ui[cache]; extra == 'all'
39
- Requires-Dist: solara-ui[extra]; extra == 'all'
40
- Requires-Dist: solara-ui[markdown]; extra == 'all'
37
+ Requires-Dist: cachetools; extra == 'all'
38
+ Requires-Dist: markdown; extra == 'all'
39
+ Requires-Dist: numpy; extra == 'all'
40
+ Requires-Dist: pillow; extra == 'all'
41
+ Requires-Dist: pygments; extra == 'all'
42
+ Requires-Dist: pygments==2.10; (python_version < '3.7') and extra == 'all'
43
+ Requires-Dist: pymdown-extensions; extra == 'all'
41
44
  Provides-Extra: cache
42
45
  Requires-Dist: cachetools; extra == 'cache'
43
46
  Provides-Extra: extra