solara 1.24.0__py2.py3-none-any.whl → 1.25.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.
Files changed (44) hide show
  1. solara/__init__.py +1 -1
  2. solara/__main__.py +4 -1
  3. solara/cache.py +9 -4
  4. solara/checks.py +9 -4
  5. solara/lab/components/__init__.py +1 -0
  6. solara/lab/components/chat.py +203 -0
  7. solara/minisettings.py +1 -1
  8. solara/server/assets/style.css +1545 -0
  9. solara/server/flask.py +1 -1
  10. solara/server/kernel.py +3 -3
  11. solara/server/patch.py +2 -0
  12. solara/server/reload.py +1 -1
  13. solara/server/server.py +58 -0
  14. solara/server/settings.py +1 -0
  15. solara/server/starlette.py +32 -13
  16. solara/server/static/solara_bootstrap.py +1 -1
  17. solara/server/telemetry.py +8 -3
  18. solara/server/templates/loader-plain.html +1 -1
  19. solara/server/templates/loader-solara.html +1 -1
  20. solara/server/templates/solara.html.j2 +20 -25
  21. solara/util.py +15 -2
  22. solara/website/components/notebook.py +44 -1
  23. solara/website/pages/__init__.py +3 -0
  24. solara/website/pages/api/__init__.py +1 -0
  25. solara/website/pages/api/chat.py +109 -0
  26. solara/website/pages/apps/jupyter-dashboard-1.py +116 -0
  27. solara/website/pages/apps/scatter.py +4 -4
  28. solara/website/pages/doc_use_download.py +1 -1
  29. solara/website/pages/docs/content/04-tutorial/00-overview.md +1 -0
  30. solara/website/pages/docs/content/04-tutorial/60-jupyter-dashboard-part1.py +18 -1
  31. solara/website/pages/docs/content/04-tutorial/_jupyter_dashboard_1.ipynb +607 -14
  32. solara/website/pages/docs/content/10-howto/ipywidget_libraries.md +1 -1
  33. solara/website/pages/docs/content/95-changelog.md +31 -0
  34. solara/website/pages/examples/ai/chatbot.py +96 -0
  35. solara/website/public/success.html +16 -7
  36. solara/website/templates/index.html.j2 +16 -15
  37. {solara-1.24.0.dist-info → solara-1.25.1.dist-info}/METADATA +9 -8
  38. {solara-1.24.0.dist-info → solara-1.25.1.dist-info}/RECORD +43 -40
  39. {solara-1.24.0.dist-info → solara-1.25.1.dist-info}/WHEEL +1 -1
  40. solara/server/assets/index.css +0 -14480
  41. {solara-1.24.0.data → solara-1.25.1.data}/data/prefix/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
  42. {solara-1.24.0.data → solara-1.25.1.data}/data/prefix/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
  43. {solara-1.24.0.dist-info → solara-1.25.1.dist-info}/entry_points.txt +0 -0
  44. {solara-1.24.0.dist-info → solara-1.25.1.dist-info}/licenses/LICENSE +0 -0
solara/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """Build webapps using IPywidgets"""
2
- __version__ = "1.24.0"
2
+ __version__ = "1.25.1"
3
3
  github_url = "https://github.com/widgetti/solara"
4
4
  git_branch = "master"
5
5
 
solara/__main__.py CHANGED
@@ -18,6 +18,8 @@ from uvicorn.main import LEVEL_CHOICES, LOOP_CHOICES
18
18
  import solara
19
19
  from solara.server import settings
20
20
 
21
+ from .server import telemetry
22
+
21
23
  try:
22
24
  from solara_enterprise.ssg import ssg_crawl
23
25
  except ImportError:
@@ -84,7 +86,7 @@ def _check_version():
84
86
  def find_all_packages_paths():
85
87
  paths = []
86
88
  # sitepackages = set([os.path.dirname(k) for k in site.getsitepackages()])
87
- sitepackages = set([k for k in site.getsitepackages()])
89
+ sitepackages = {k for k in site.getsitepackages()}
88
90
  paths.extend(list(sitepackages))
89
91
  for name, module in sys.modules.items():
90
92
  if hasattr(module, "__path__"):
@@ -287,6 +289,7 @@ def run(
287
289
 
288
290
  failed = False
289
291
  if reload:
292
+ telemetry._auto_restart_enabled = True
290
293
  solara_root = Path(solara.__file__).parent
291
294
 
292
295
  reload_dirs = list(reload_dirs if reload_dirs else [])
solara/cache.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import hashlib
2
2
  import inspect
3
3
  import logging
4
+ import sys
4
5
  from typing import (
5
6
  Any,
6
7
  Callable,
@@ -15,12 +16,11 @@ from typing import (
15
16
  )
16
17
 
17
18
  import cachetools
18
- import typing_extensions
19
- from reacton.utils import equals
20
-
21
19
  import solara
22
20
  import solara.settings
23
21
  import solara.util
22
+ import typing_extensions
23
+ from reacton.utils import equals
24
24
 
25
25
  logger = logging.getLogger("solara.cache")
26
26
 
@@ -59,7 +59,12 @@ class MemoizedFunction(Generic[P, R]):
59
59
  nonlocals = inspect.getclosurevars(f).nonlocals
60
60
  if nonlocals:
61
61
  raise ValueError(f"Memoized functions cannot depend on nonlocal variables, it now depends on {nonlocals}")
62
- codehash = hashlib.md5(f.__code__.co_code).hexdigest()
62
+ if sys.version_info[:2] < (3, 9):
63
+ # usedforsecurity is only available in Python 3.9+
64
+ codehash = hashlib.md5(f.__code__.co_code).hexdigest()
65
+ else:
66
+ codehash = hashlib.md5(f.__code__.co_code, usedforsecurity=False).hexdigest() # type: ignore
67
+
63
68
  self.function_key = (f.__qualname__, codehash)
64
69
  current_globals = dict(inspect.getclosurevars(f).globals)
65
70
  _global_values_used.setdefault(self.function_key, current_globals)
solara/checks.py CHANGED
@@ -19,6 +19,7 @@ logger = logging.getLogger(__name__)
19
19
 
20
20
  jupyter_checked_path = get_solara_home() / ".jupyter_checked"
21
21
  solara_checked_path = get_solara_home() / ".solara_checked"
22
+ solara_version = solara.__version__
22
23
 
23
24
 
24
25
  def _should_perform_check(path: Path):
@@ -67,12 +68,12 @@ def JupyterCheck():
67
68
  # if the widgets do not work
68
69
  IPython.display.display(
69
70
  IPython.display.Javascript(
70
- data="""
71
+ data=f"""
71
72
  const prevIframe = document.getElementById("solara-jupyter-check");
72
73
  if(prevIframe)
73
74
  prevIframe.remove();
74
75
  const iframe = document.createElement('iframe')
75
- iframe.setAttribute("src", "https://solara.dev/static/public/success.html?check=purejs");
76
+ iframe.setAttribute("src", "https://solara.dev/static/public/success.html?check=purejs&version={solara_version}");
76
77
  iframe.style.width = "0px";
77
78
  iframe.style.height = "0px";
78
79
  iframe.style.display = "none";
@@ -95,7 +96,7 @@ document.body.appendChild(iframe);
95
96
  # this iframe should only get through if the widget installation succeeded
96
97
  return solara.v.Html(
97
98
  tag="iframe",
98
- attributes={"src": "https://solara.dev/static/public/success.html?check=widget", "width": "0px", "height": "0px"},
99
+ attributes={"src": f"https://solara.dev/static/public/success.html?check=widget&version={solara_version}", "width": "0px", "height": "0px"},
99
100
  style_="display: none;",
100
101
  )
101
102
 
@@ -111,7 +112,11 @@ def SolaraCheck():
111
112
  solara.use_effect(flag_solara_checked, [])
112
113
  return solara.v.Html(
113
114
  tag="iframe",
114
- attributes={"src": "https://solara.dev/static/public/success.html?system=solara&check=widget", "width": "0px", "height": "0px"},
115
+ attributes={
116
+ "src": f"https://solara.dev/static/public/success.html?system=solara&check=widget&version={solara_version}",
117
+ "width": "0px",
118
+ "height": "0px",
119
+ },
115
120
  style_="display: none;",
116
121
  )
117
122
 
@@ -1,3 +1,4 @@
1
+ from .chat import ChatBox, ChatInput, ChatMessage # noqa: F401
1
2
  from .confirmation_dialog import ConfirmationDialog # noqa: F401
2
3
  from .input_date import InputDate, InputDateRange # noqa: F401
3
4
  from .menu import ClickMenu, ContextMenu, Menu # noqa: F401 F403
@@ -0,0 +1,203 @@
1
+ import uuid
2
+ from typing import Callable, Dict, List, Optional, Union
3
+
4
+ from typing_extensions import Literal
5
+
6
+ import solara
7
+ from solara.components.input import use_change
8
+
9
+
10
+ @solara.component
11
+ def ChatBox(
12
+ children: List[solara.Element] = [],
13
+ style: Optional[Union[str, Dict[str, str]]] = None,
14
+ classes: List[str] = [],
15
+ ):
16
+ """
17
+ The ChatBox component is a container for ChatMessage components.
18
+ Its primary use is to ensure the proper ordering of messages,
19
+ using `flex-direction: column-reverse` together with `reversed(messages)`.
20
+
21
+ # Arguments
22
+
23
+ * `children`: A list of child components.
24
+ * `style`: CSS styles to apply to the component. Either a string or a dictionary.
25
+ * `classes`: A list of CSS classes to apply to the component.
26
+ """
27
+ style_flat = solara.util._flatten_style(style)
28
+ if "flex-grow" not in style_flat:
29
+ style_flat += " flex-grow: 1;"
30
+ if "flex-direction" not in style_flat:
31
+ style_flat += " flex-direction: column-reverse;"
32
+ if "overflow-y" not in style_flat:
33
+ style_flat += " overflow-y: auto;"
34
+
35
+ classes += ["chat-box"]
36
+ with solara.Column(
37
+ style=style_flat,
38
+ classes=classes,
39
+ ):
40
+ for child in list(reversed(children)):
41
+ solara.display(child)
42
+
43
+
44
+ @solara.component
45
+ def ChatInput(
46
+ send_callback: Optional[Callable] = None,
47
+ disabled: bool = False,
48
+ style: Optional[Union[str, Dict[str, str]]] = None,
49
+ classes: List[str] = [],
50
+ ):
51
+ """
52
+ The ChatInput component renders a text input together with a send button.
53
+
54
+ # Arguments
55
+
56
+ * `send_callback`: A callback function for when the user presses enter or clicks the send button.
57
+ * `disabled`: Whether the input should be disabled. Useful for disabling sending further messages while a chatbot is replying,
58
+ among other things.
59
+ * `style`: CSS styles to apply to the component. Either a string or a dictionary. These styles are applied to the container component.
60
+ * `classes`: A list of CSS classes to apply to the component. Also applied to the container.
61
+ """
62
+ message, set_message = solara.use_state("") # type: ignore
63
+ style_flat = solara.util._flatten_style(style)
64
+
65
+ if "align-items" not in style_flat:
66
+ style_flat += " align-items: center;"
67
+
68
+ with solara.Row(style=style_flat, classes=classes):
69
+
70
+ def send(*ignore_args):
71
+ if message != "" and send_callback is not None:
72
+ send_callback(message)
73
+ set_message("")
74
+
75
+ message_input = solara.v.TextField(
76
+ label="Type a message...",
77
+ v_model=message,
78
+ on_v_model=set_message,
79
+ rounded=True,
80
+ filled=True,
81
+ hide_details=True,
82
+ style_="flex-grow: 1;",
83
+ disabled=disabled,
84
+ )
85
+
86
+ use_change(message_input, send, update_events=["keyup.enter"])
87
+
88
+ button = solara.v.Btn(color="primary", icon=True, children=[solara.v.Icon(children=["mdi-send"])], disabled=message == "")
89
+
90
+ use_change(button, send, update_events=["click"])
91
+
92
+
93
+ @solara.component
94
+ def ChatMessage(
95
+ children: Union[List[solara.Element], str],
96
+ user: bool = False,
97
+ avatar: Union[solara.Element, str, Literal[False], None] = None,
98
+ name: Optional[str] = None,
99
+ color: Optional[str] = "rgba(0,0,0,.06)",
100
+ avatar_background_color: Optional[str] = None,
101
+ border_radius: Optional[str] = None,
102
+ notch: bool = False,
103
+ style: Optional[Union[str, Dict[str, str]]] = None,
104
+ classes: List[str] = [],
105
+ ):
106
+ """
107
+ The ChatMessage component renders a message. Messages with `user=True` are rendered on the right side of the screen,
108
+ all others on the left.
109
+
110
+ # Arguments
111
+
112
+ * `children`: A list of child components.
113
+ * `user`: Whether the message is from the current user or not.
114
+ * `avatar`: An avatar to display next to the message. Can be a string representation of a URL or Material design icon name,
115
+ a solara Element, False to disable avatars altogether, or None to display initials based on `name`.
116
+ * `name`: The name of the user who sent the message.
117
+ * `color`: The background color of the message. Defaults to `rgba(0,0,0,.06)`. Can be any valid CSS color.
118
+ * `avatar_background_color`: The background color of the avatar. Defaults to `color` if left as `None`.
119
+ * `border_radius`: Sets the roundness of the corners of the message. Defaults to `None`,
120
+ which applies the default border radius of a `solara.Column`, i.e. `4px`.
121
+ * `notch`: Whether to display a speech bubble style notch on the side of the message.
122
+ * `style`: CSS styles to apply to the component. Either a string or a dictionary. Applied to the container of the message.
123
+ * `classes`: A list of CSS classes to apply to the component. Applied to the same container.
124
+ """
125
+ style_flat = solara.util._flatten_style(style)
126
+
127
+ if "border-radius" not in style_flat:
128
+ style_flat += f" border-radius: {border_radius if border_radius is not None else ''};"
129
+ if f"border-top-{'right' if user else 'left'}-radius" not in style_flat:
130
+ style_flat += f" border-top-{'right' if user else 'left'}-radius: 0;"
131
+ if "padding" not in style_flat:
132
+ style_flat += " padding: .5em 1.5em;"
133
+
134
+ msg_uuid = solara.use_memo(lambda: str(uuid.uuid4()), dependencies=[])
135
+ with solara.Row(
136
+ justify="end" if user else "start",
137
+ style={"flex-direction": "row-reverse" if user else "row", "padding": "5px"},
138
+ ):
139
+ if avatar is not False:
140
+ with solara.v.Avatar(color=avatar_background_color if avatar_background_color is not None else color):
141
+ if avatar is None and name is not None:
142
+ initials = "".join([word[:1] for word in name.split(" ")])
143
+ solara.HTML(tag="span", unsafe_innerHTML=initials, classes=["headline"])
144
+ elif isinstance(avatar, solara.Element):
145
+ solara.display(avatar)
146
+ elif isinstance(avatar, str) and avatar.startswith("mdi-"):
147
+ solara.v.Icon(children=[avatar])
148
+ else:
149
+ solara.HTML(tag="img", attributes={"src": avatar, "width": "100%"})
150
+ classes_new = classes + ["chat-message-" + msg_uuid, "right" if user else "left"]
151
+ with solara.Column(
152
+ classes=classes_new,
153
+ gap=0,
154
+ style=style_flat,
155
+ ):
156
+ if name is not None:
157
+ solara.Text(name, style="font-weight: bold;", classes=["message-name", "right" if user else "left"])
158
+ for child in children:
159
+ if isinstance(child, solara.Element):
160
+ solara.display(child)
161
+ else:
162
+ solara.Markdown(child)
163
+ # we use the uuid to generate 'scoped' CSS, i.e. css that only applies to the component instance.
164
+ extra_styles = (
165
+ f""".chat-message-{msg_uuid}:before{{
166
+ content: '';
167
+ position: absolute;
168
+ width: 0;
169
+ height: 0;
170
+ border: 6px solid;
171
+ top: 0;
172
+ }}
173
+ .chat-message-{msg_uuid}.left:before{{
174
+ left: -12px;
175
+ border-color: var(--color) var(--color) transparent transparent;
176
+ }}
177
+ .chat-message-{msg_uuid}.right:before{{
178
+ right: -12px;
179
+ border-color: var(--color) transparent transparent var(--color);
180
+ }}"""
181
+ if notch
182
+ else ""
183
+ )
184
+ solara.Style(
185
+ f"""
186
+ .chat-message-{msg_uuid}{{
187
+ --color: {color};
188
+ max-width: 75%;
189
+ position: relative;
190
+ }}
191
+ .chat-message-{msg_uuid}.left{{
192
+ border-top-left-radius: 0;
193
+ background-color:var(--color);
194
+ { "margin-left: 10px !important;" if notch else ""}
195
+ }}
196
+ .chat-message-{msg_uuid}.right{{
197
+ border-top-right-radius: 0;
198
+ background-color:var(--color);
199
+ { "margin-right: 10px !important;" if notch else ""}
200
+ }}
201
+ {extra_styles}
202
+ """
203
+ )
solara/minisettings.py CHANGED
@@ -94,7 +94,7 @@ class BaseSettings:
94
94
  def __init__(self, **kwargs) -> None:
95
95
  cls = type(self)
96
96
  self._values = {**kwargs}
97
- keys = set([k.upper() for k in os.environ.keys()])
97
+ keys = {k.upper() for k in os.environ.keys()}
98
98
  for key, field in cls.__dict__.items():
99
99
  if key in kwargs:
100
100
  continue