solara 1.30.0__py2.py3-none-any.whl → 1.30.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/autorouting.py +58 -67
- solara/components/component_vue.py +8 -7
- solara/components/image.py +1 -1
- solara/scope/__init__.py +3 -2
- solara/server/kernel_context.py +3 -0
- solara/server/static/solara_bootstrap.py +1 -1
- solara/website/components/markdown.py +30 -0
- solara/website/pages/__init__.py +18 -7
- solara/website/pages/changelog/changelog.md +15 -0
- solara/website/pages/documentation/advanced/__init__.py +2 -29
- solara/website/pages/documentation/advanced/content/10-howto/00-overview.md +5 -0
- solara/website/pages/documentation/advanced/content/10-howto/10-multipage.md +5 -0
- solara/website/pages/documentation/advanced/content/10-howto/20-layout.md +6 -1
- solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +5 -0
- solara/website/pages/documentation/advanced/content/10-howto/31-debugging.md +4 -1
- solara/website/pages/documentation/advanced/content/10-howto/40-embed.md +6 -0
- solara/website/pages/documentation/advanced/content/10-howto/50-ipywidget_libraries.md +4 -0
- solara/website/pages/documentation/advanced/content/20-understanding/00-introduction.md +6 -0
- solara/website/pages/documentation/advanced/content/20-understanding/05-ipywidgets.md +5 -0
- solara/website/pages/documentation/advanced/content/20-understanding/06-ipyvuetify.md +4 -0
- solara/website/pages/documentation/advanced/content/20-understanding/10-reacton.md +4 -0
- solara/website/pages/documentation/advanced/content/20-understanding/12-reacton-basics.md +4 -0
- solara/website/pages/documentation/advanced/content/20-understanding/15-anatomy.md +4 -0
- solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +4 -0
- solara/website/pages/documentation/advanced/content/20-understanding/18-containers.md +6 -0
- solara/website/pages/documentation/advanced/content/20-understanding/20-solara.md +5 -0
- solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +4 -0
- solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +5 -0
- solara/website/pages/documentation/advanced/content/20-understanding/60-voila.md +5 -0
- solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +4 -0
- solara/website/pages/documentation/advanced/content/40-development/01-contribute.md +4 -0
- solara/website/pages/documentation/advanced/content/40-development/10-setup.md +4 -0
- solara/website/pages/documentation/examples/general/custom_storage.py +1 -1
- solara/website/pages/documentation/faq/__init__.py +2 -1
- solara/website/pages/documentation/faq/content/99-faq.md +4 -1
- solara/website/pages/documentation/getting_started/__init__.py +2 -8
- solara/website/pages/documentation/getting_started/content/00-quickstart.md +5 -0
- solara/website/pages/documentation/getting_started/content/01-introduction.md +4 -0
- solara/website/pages/documentation/getting_started/content/02-installing.md +4 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/00-overview.md +5 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/20-web-app.md +5 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/30-ipywidgets.md +4 -0
- solara/website/pages/documentation/getting_started/content/04-tutorials/40-streamlit.md +6 -1
- solara/website/pages/documentation/getting_started/content/04-tutorials/50-dash.md +4 -0
- solara/website/pages/documentation/getting_started/content/05-fundamentals/00-overview.md +4 -0
- solara/website/pages/documentation/getting_started/content/05-fundamentals/10-components.md +5 -0
- solara/website/pages/documentation/getting_started/content/05-fundamentals/50-state-management.md +5 -0
- solara/website/pages/documentation/getting_started/content/06-reference/40-static_files.md +4 -0
- solara/website/pages/documentation/getting_started/content/06-reference/41-asset-files.md +4 -0
- solara/website/pages/documentation/getting_started/content/06-reference/60-static-site-generation.md +4 -0
- solara/website/pages/documentation/getting_started/content/06-reference/70-search.md +4 -0
- solara/website/pages/documentation/getting_started/content/06-reference/80-reloading.md +4 -0
- solara/website/pages/documentation/getting_started/content/06-reference/90-notebook-support.md +4 -1
- solara/website/pages/documentation/getting_started/content/06-reference/95-caching.md +5 -0
- solara/website/pages/documentation/getting_started/content/07-deploying/00-overview.md +4 -0
- solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +4 -0
- solara/website/pages/documentation/getting_started/content/07-deploying/20-cloud-hosted.md +4 -0
- solara/website/pages/documentation/getting_started/content/80-what-is-lab.md +7 -0
- solara/website/pages/documentation/getting_started/content/90-troubleshoot.md +4 -0
- {solara-1.30.0.dist-info → solara-1.30.1.dist-info}/METADATA +2 -2
- {solara-1.30.0.dist-info → solara-1.30.1.dist-info}/RECORD +67 -68
- solara/website/pages/documentation/advanced/content/__init__.py +0 -0
- solara/website/pages/documentation/getting_started/content/08-lab/00-what-is-lab.md +0 -3
- solara/website/pages/documentation/getting_started/content/__init__.py +0 -0
- {solara-1.30.0.data → solara-1.30.1.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
- {solara-1.30.0.data → solara-1.30.1.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
- {solara-1.30.0.dist-info → solara-1.30.1.dist-info}/WHEEL +0 -0
- {solara-1.30.0.dist-info → solara-1.30.1.dist-info}/entry_points.txt +0 -0
- {solara-1.30.0.dist-info → solara-1.30.1.dist-info}/licenses/LICENSE +0 -0
solara/__init__.py
CHANGED
solara/autorouting.py
CHANGED
|
@@ -24,6 +24,7 @@ DEBUG = False
|
|
|
24
24
|
solara_root = Path(solara.__file__).parent
|
|
25
25
|
|
|
26
26
|
DefaultLayout = solara.AppLayout
|
|
27
|
+
_redirects: Dict[str, str] = {}
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def source_to_module(path: Path, initial_namespace={}) -> ModuleType:
|
|
@@ -89,12 +90,17 @@ def arg_cast(args: List[str], f: Callable):
|
|
|
89
90
|
def RoutingProvider(children: List[reacton.core.Element] = [], routes: List[solara.Route] = [], pathname: str = ""):
|
|
90
91
|
"""Wraps the app, adds extra context, like navigation/routing."""
|
|
91
92
|
path, set_path = solara.use_state(pathname, key="solara-context-path")
|
|
93
|
+
|
|
94
|
+
def set_path_with_redirect(path):
|
|
95
|
+
path = _redirects.get(path, path)
|
|
96
|
+
set_path(path)
|
|
97
|
+
|
|
92
98
|
# TODO: since we provide a cross filter context here, I don't think name `RoutingProvider` is a good name
|
|
93
99
|
# we might want to change/refactor this.
|
|
94
100
|
solara.provide_cross_filter()
|
|
95
|
-
nav = solara.Navigator(location=path, on_location=
|
|
96
|
-
solara.routing._location_context.provide(solara.routing._Location(path,
|
|
97
|
-
solara.routing.router_context.provide(solara.routing.Router(path, routes=routes, set_path=
|
|
101
|
+
nav = solara.Navigator(location=path, on_location=set_path_with_redirect)
|
|
102
|
+
solara.routing._location_context.provide(solara.routing._Location(path, set_path_with_redirect))
|
|
103
|
+
solara.routing.router_context.provide(solara.routing.Router(path, routes=routes, set_path=set_path_with_redirect))
|
|
98
104
|
|
|
99
105
|
def get_nav_widget():
|
|
100
106
|
# not sure why get_widget(nav) does not work
|
|
@@ -145,7 +151,7 @@ def RenderPage(main_name: str = "Page"):
|
|
|
145
151
|
|
|
146
152
|
layouts = [] # nested layouts
|
|
147
153
|
level_max = len(router.path_routes) - 1
|
|
148
|
-
for level in range(
|
|
154
|
+
for level in range(len(router.path_routes)):
|
|
149
155
|
# we always level_start the 'package'/'root' layout
|
|
150
156
|
roots = [k for k in router.path_routes_siblings[level] if k.path == "/"]
|
|
151
157
|
if len(roots) == 1:
|
|
@@ -156,20 +162,21 @@ def RenderPage(main_name: str = "Page"):
|
|
|
156
162
|
# and, if not the root layout, include the layout for the current route
|
|
157
163
|
if route.path != "/" and route.layout:
|
|
158
164
|
layouts.append(route.layout)
|
|
159
|
-
#
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
level_max = level - 1
|
|
165
|
+
# We found the leaf (or node) that defines a component or markdown file
|
|
166
|
+
if (route.file and route.file.suffix == ".md") or route.component:
|
|
167
|
+
level_max = level
|
|
168
|
+
break
|
|
169
|
+
|
|
165
170
|
route_current = router.path_routes[level_max]
|
|
166
171
|
routes_siblings = router.path_routes_siblings[level_max]
|
|
167
172
|
routes_siblings_index = routes_siblings.index(route_current)
|
|
173
|
+
|
|
168
174
|
# if no layouts are found, we use the default layout
|
|
169
175
|
if layouts == []:
|
|
170
176
|
layouts = [DefaultLayout]
|
|
171
|
-
|
|
172
|
-
|
|
177
|
+
|
|
178
|
+
if route_current.component is None and route_current.module is None and (route_current.file is None or route_current.file.suffix != ".md"):
|
|
179
|
+
return solara.Error(f"Page not found: {router.path}, route does not link to a (markdown) path or module or component")
|
|
173
180
|
|
|
174
181
|
def wrap_in_layouts(element: reacton.core.Element, layouts):
|
|
175
182
|
for Layout in reversed(layouts):
|
|
@@ -184,9 +191,10 @@ def RenderPage(main_name: str = "Page"):
|
|
|
184
191
|
else:
|
|
185
192
|
return []
|
|
186
193
|
|
|
187
|
-
if isinstance(route_current.data, Path):
|
|
194
|
+
if str(route_current.file).endswith(".md") or isinstance(route_current.data, Path):
|
|
188
195
|
path = cast(Path, route_current.data)
|
|
189
196
|
if path.suffix == ".md":
|
|
197
|
+
component = route_current.component or solara.Markdown
|
|
190
198
|
with solara.HBox() as navigation:
|
|
191
199
|
if routes_siblings_index > 0:
|
|
192
200
|
prev = routes_siblings[routes_siblings_index - 1]
|
|
@@ -206,9 +214,11 @@ def RenderPage(main_name: str = "Page"):
|
|
|
206
214
|
solara.Button(
|
|
207
215
|
icon_name="mdi-pencil", icon=True, href=url, target="_blank", style={"position": "absolute", "top": "0px", "right": "0px"}
|
|
208
216
|
)
|
|
209
|
-
solara.Markdown(path.read_text(), unsafe_solara_execute=True)
|
|
217
|
+
# solara.Markdown(path.read_text(), unsafe_solara_execute=True)
|
|
218
|
+
component(path.read_text(), unsafe_solara_execute=True)
|
|
210
219
|
else:
|
|
211
|
-
content = solara.Markdown(path.read_text(), unsafe_solara_execute=True)
|
|
220
|
+
# content = solara.Markdown(path.read_text(), unsafe_solara_execute=True)
|
|
221
|
+
content = component(path.read_text(), unsafe_solara_execute=True)
|
|
212
222
|
|
|
213
223
|
main = solara.Div(
|
|
214
224
|
classes=["solara-autorouter-content"],
|
|
@@ -401,7 +411,6 @@ def generate_routes(module: ModuleType) -> List[solara.Route]:
|
|
|
401
411
|
reload.reloader.watcher.add_file(module.__file__)
|
|
402
412
|
for info in pkgutil.iter_modules([str(Path(module.__file__).parent)]):
|
|
403
413
|
submod = importlib.import_module(module.__name__ + f".{info.name}")
|
|
404
|
-
subfile = Path(submod.__file__) if submod.__file__ is not None else None
|
|
405
414
|
title = get_title(submod)
|
|
406
415
|
|
|
407
416
|
name = info.name
|
|
@@ -409,9 +418,11 @@ def generate_routes(module: ModuleType) -> List[solara.Route]:
|
|
|
409
418
|
# however, this may break things.
|
|
410
419
|
# name = name.replace("_", "-")
|
|
411
420
|
if info.ispkg:
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
421
|
+
# we are in a package, like 'portal/solara_portal/pages', not the module itself
|
|
422
|
+
# (e.g. portal/solara_portal/pages/__init__.py)
|
|
423
|
+
# so here name='pages', and the children will come from the submodules
|
|
424
|
+
children = generate_routes(submod)
|
|
425
|
+
route = solara.Route(name, component=None, children=children, module=submod, layout=None, label=title)
|
|
415
426
|
# skip empty subpackages
|
|
416
427
|
if len(route.children) == 0:
|
|
417
428
|
continue
|
|
@@ -419,13 +430,7 @@ def generate_routes(module: ModuleType) -> List[solara.Route]:
|
|
|
419
430
|
# skip empty modules
|
|
420
431
|
if get_renderable(submod) is None and not hasattr(submod, "routes"):
|
|
421
432
|
continue
|
|
422
|
-
|
|
423
|
-
if subfile:
|
|
424
|
-
children = fix_routes(children, subfile)
|
|
425
|
-
module_layout = getattr(submod, "Layout", None)
|
|
426
|
-
route = solara.Route(
|
|
427
|
-
name, component=get_page(submod, required=False), module=submod, layout=module_layout, children=children, label=title, file=subfile
|
|
428
|
-
)
|
|
433
|
+
route = _generate_route(name, submod)
|
|
429
434
|
routes.append(route)
|
|
430
435
|
if route_order:
|
|
431
436
|
lookup = {k.path: k for k in routes}
|
|
@@ -437,32 +442,12 @@ def generate_routes(module: ModuleType) -> List[solara.Route]:
|
|
|
437
442
|
warnings.warn(f"Some routes are not in route_order: {set(lookup) - set(route_order)}")
|
|
438
443
|
|
|
439
444
|
else:
|
|
440
|
-
|
|
441
|
-
children = []
|
|
442
|
-
if hasattr(module, "routes"):
|
|
443
|
-
children = getattr(module, "routes", [])
|
|
444
|
-
root = get_root(children)
|
|
445
|
-
if layout is not None and root is not None and root.layout is None:
|
|
446
|
-
warnings.warn(f'You defined routes in {file}, in this case, layout should be set on the root route (with path="/"), not on the module level')
|
|
447
|
-
layout = None
|
|
448
|
-
children = fix_routes(children, file, layout)
|
|
449
|
-
return [
|
|
450
|
-
solara.Route(
|
|
451
|
-
path="/",
|
|
452
|
-
component=get_page(module, required=False),
|
|
453
|
-
data=None,
|
|
454
|
-
module=module,
|
|
455
|
-
label=get_title(module),
|
|
456
|
-
layout=layout,
|
|
457
|
-
children=children,
|
|
458
|
-
file=file,
|
|
459
|
-
)
|
|
460
|
-
]
|
|
445
|
+
return [_generate_route("/", module)]
|
|
461
446
|
|
|
462
447
|
return routes
|
|
463
448
|
|
|
464
449
|
|
|
465
|
-
def generate_routes_directory(path: Path) -> List[solara.Route]:
|
|
450
|
+
def generate_routes_directory(path: Path, markdown_component=None) -> List[solara.Route]:
|
|
466
451
|
"""Generate routes for a directory.
|
|
467
452
|
|
|
468
453
|
This is a recursive function that will generate routes for all
|
|
@@ -495,13 +480,13 @@ def generate_routes_directory(path: Path) -> List[solara.Route]:
|
|
|
495
480
|
continue
|
|
496
481
|
if subpath.stem.startswith("_") or subpath.stem.startswith("."):
|
|
497
482
|
continue
|
|
498
|
-
route = _generate_route_path(subpath, layout=layout, first=first, has_index=has_index)
|
|
483
|
+
route = _generate_route_path(subpath, layout=layout, first=first, has_index=has_index, markdown_component=markdown_component)
|
|
499
484
|
first = False
|
|
500
485
|
routes.append(route)
|
|
501
486
|
return routes
|
|
502
487
|
|
|
503
488
|
|
|
504
|
-
def _generate_route_path(subpath: Path, layout=None, first=False, has_index=False, initial_namespace={}) -> solara.Route:
|
|
489
|
+
def _generate_route_path(subpath: Path, layout=None, first=False, has_index=False, initial_namespace={}, markdown_component=None) -> solara.Route:
|
|
505
490
|
from .server import reload
|
|
506
491
|
|
|
507
492
|
name = subpath.stem
|
|
@@ -521,32 +506,38 @@ def _generate_route_path(subpath: Path, layout=None, first=False, has_index=Fals
|
|
|
521
506
|
module_layout = layout if first else None
|
|
522
507
|
if subpath.suffix == ".md":
|
|
523
508
|
data = subpath
|
|
509
|
+
component = markdown_component
|
|
524
510
|
reload.reloader.watcher.add_file(subpath)
|
|
525
511
|
elif subpath.is_dir():
|
|
526
|
-
children = generate_routes_directory(subpath)
|
|
512
|
+
children = generate_routes_directory(subpath, markdown_component=markdown_component)
|
|
527
513
|
else:
|
|
528
514
|
reload.reloader.watcher.add_file(subpath)
|
|
529
515
|
module = source_to_module(subpath, initial_namespace=initial_namespace)
|
|
530
|
-
|
|
531
|
-
layout = getattr(module, "Layout", module_layout)
|
|
532
|
-
root = get_root(children)
|
|
533
|
-
if hasattr(module, "routes"):
|
|
534
|
-
children = getattr(module, "routes", [])
|
|
535
|
-
root = get_root(children)
|
|
536
|
-
if layout is not None and root is not None and root.layout is None:
|
|
537
|
-
warnings.warn(f'You defined routes in {subpath}, in this case, layout should be set on the root route (with path="/"), not on the module level')
|
|
538
|
-
layout = None
|
|
539
|
-
children = fix_routes(children, subpath, layout)
|
|
540
|
-
component = get_page(module, required=False)
|
|
541
|
-
if root and component and root.component and component is not root.component:
|
|
542
|
-
warnings.warn(
|
|
543
|
-
f"In {subpath}, you defined a Page component, but also a component on the root route (with path='/') "
|
|
544
|
-
"which is not equal to the Page component at the module level. This is not recommended."
|
|
545
|
-
)
|
|
516
|
+
return _generate_route(route_path, module, default_layout=module_layout)
|
|
546
517
|
route = solara.Route(route_path, component=component, module=module, label=title, children=children, data=data, layout=layout, file=subpath)
|
|
547
518
|
return route
|
|
548
519
|
|
|
549
520
|
|
|
521
|
+
def _generate_route(route_path: str, module: ModuleType, default_layout=None, data=None) -> solara.Route:
|
|
522
|
+
path = Path(module.__file__) if module.__file__ is not None else None
|
|
523
|
+
title = get_title(module)
|
|
524
|
+
layout = getattr(module, "Layout", default_layout)
|
|
525
|
+
if inspect.isclass(layout) and issubclass(layout, ipywidgets.Layout):
|
|
526
|
+
layout = None
|
|
527
|
+
component = None
|
|
528
|
+
children = getattr(module, "routes", [])
|
|
529
|
+
root_route = get_root(children)
|
|
530
|
+
# if we have no children or the children have no explicit root component (at '/')
|
|
531
|
+
if not children or (root_route and root_route.component is None):
|
|
532
|
+
component = get_page(module, required=False)
|
|
533
|
+
if root_route and component and root_route.component and component is not root_route.component:
|
|
534
|
+
warnings.warn(
|
|
535
|
+
f"In {path}, you defined a Page component, but also a component on the root route (with path='/') "
|
|
536
|
+
"which is not equal to the Page component at the module level. This is not recommended."
|
|
537
|
+
)
|
|
538
|
+
return solara.Route(route_path, component=component, module=module, label=title, children=children, data=data, layout=layout, file=path)
|
|
539
|
+
|
|
540
|
+
|
|
550
541
|
def get_root(routes: List[solara.Route]) -> Optional[solara.Route]:
|
|
551
542
|
for route in routes:
|
|
552
543
|
if route.path == "/":
|
|
@@ -20,8 +20,8 @@ def _widget_from_signature(classname, base_class: Type[widgets.Widget], func: Ca
|
|
|
20
20
|
if name.startswith("event_"):
|
|
21
21
|
event_name = name[6:]
|
|
22
22
|
|
|
23
|
-
def event_handler(self, data, buffers=None, event_name=event_name):
|
|
24
|
-
callback = self._event_callbacks.get(event_name)
|
|
23
|
+
def event_handler(self, data, buffers=None, event_name=event_name, param=param):
|
|
24
|
+
callback = self._event_callbacks.get(event_name, param.default)
|
|
25
25
|
if callback:
|
|
26
26
|
if buffers:
|
|
27
27
|
callback(data, buffers)
|
|
@@ -29,14 +29,15 @@ def _widget_from_signature(classname, base_class: Type[widgets.Widget], func: Ca
|
|
|
29
29
|
callback(data)
|
|
30
30
|
|
|
31
31
|
classprops[f"vue_{event_name}"] = event_handler
|
|
32
|
-
|
|
32
|
+
elif name.startswith("on_") and name[3:] in parameters:
|
|
33
33
|
# callback, will be handled by reacton
|
|
34
34
|
continue
|
|
35
|
-
if param.default == inspect.Parameter.empty:
|
|
36
|
-
trait = traitlets.Any()
|
|
37
35
|
else:
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
if param.default == inspect.Parameter.empty:
|
|
37
|
+
trait = traitlets.Any()
|
|
38
|
+
else:
|
|
39
|
+
trait = traitlets.Any(default_value=param.default)
|
|
40
|
+
classprops[name] = trait.tag(sync=True, **widgets.widget_serialization)
|
|
40
41
|
# maps event_foo to a callable
|
|
41
42
|
classprops["_event_callbacks"] = traitlets.Dict(default_value={})
|
|
42
43
|
|
solara/components/image.py
CHANGED
|
@@ -163,7 +163,7 @@ def Image(
|
|
|
163
163
|
layout=layout,
|
|
164
164
|
)
|
|
165
165
|
elif solara.util.isinstanceof(image, "numpy:ndarray"):
|
|
166
|
-
value = solara.util.numpy_to_image(image, format="png")
|
|
166
|
+
value = solara.util.numpy_to_image(image, format="png") # type: ignore
|
|
167
167
|
return rw.Image(
|
|
168
168
|
value=value,
|
|
169
169
|
format="png",
|
solara/scope/__init__.py
CHANGED
|
@@ -50,7 +50,7 @@ def get_kernel_id(ipython_fallback=True) -> str:
|
|
|
50
50
|
See [Understanding solara server](/docs/understanding/solara-server) for understanding the concept of virtual kernels
|
|
51
51
|
and their lifetime.
|
|
52
52
|
|
|
53
|
-
This unique ID can be useful to to implement storing state, scoped to a kernel. See [the scope example](/examples/general/
|
|
53
|
+
This unique ID can be useful to to implement storing state, scoped to a kernel. See [the scope example](/examples/general/custom_storage) for an example.
|
|
54
54
|
|
|
55
55
|
If `ipython_fallback` is `True` (default), this function will also work in IPython notebooks, where it will return the IPython kernel id.
|
|
56
56
|
|
|
@@ -79,7 +79,8 @@ def get_session_id() -> str:
|
|
|
79
79
|
|
|
80
80
|
See [Understanding solara server](/docs/understanding/solara-server#session) for more information about the Solara sessions.
|
|
81
81
|
|
|
82
|
-
This unique ID can be useful to to implement storing state, scoped to a browser session. See [the scope example](/examples/general/
|
|
82
|
+
This unique ID can be useful to to implement storing state, scoped to a browser session. See [the scope example](/examples/general/custom_storage)
|
|
83
|
+
for an example.
|
|
83
84
|
"""
|
|
84
85
|
import solara.server.kernel_context
|
|
85
86
|
|
solara/server/kernel_context.py
CHANGED
|
@@ -150,6 +150,9 @@ class VirtualKernelContext:
|
|
|
150
150
|
current_context[key] = local.kernel_context_stack.pop()
|
|
151
151
|
|
|
152
152
|
def close(self):
|
|
153
|
+
if self.closed_event.is_set():
|
|
154
|
+
logger.error("Tried to close a kernel context that is already closed: %s", self.id)
|
|
155
|
+
return
|
|
153
156
|
logger.info("Shut down virtual kernel: %s", self.id)
|
|
154
157
|
with self:
|
|
155
158
|
for f in reversed(self._on_close_callbacks):
|
|
@@ -119,7 +119,7 @@ async def main():
|
|
|
119
119
|
]
|
|
120
120
|
for dep in requirements:
|
|
121
121
|
await micropip.install(dep, keep_going=True)
|
|
122
|
-
await micropip.install("/wheels/solara-1.30.
|
|
122
|
+
await micropip.install("/wheels/solara-1.30.1-py2.py3-none-any.whl", keep_going=True)
|
|
123
123
|
import solara
|
|
124
124
|
|
|
125
125
|
el = solara.Warning("lala")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Dict, List, Union
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
|
|
5
|
+
import solara
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# We want to separate metadata from the markdown files before rendering them, which solara.Markdown doesn't support
|
|
9
|
+
@solara.component
|
|
10
|
+
def MarkdownWithMetadata(content: str, unsafe_solara_execute=True):
|
|
11
|
+
if "---" in content:
|
|
12
|
+
pre_content, raw_metadata, post_content = content.split("---")
|
|
13
|
+
metadata: Dict[str, Union[str, List[str]]] = yaml.safe_load(raw_metadata)
|
|
14
|
+
|
|
15
|
+
if len(pre_content) == 0:
|
|
16
|
+
content = post_content
|
|
17
|
+
else:
|
|
18
|
+
content = pre_content + post_content
|
|
19
|
+
|
|
20
|
+
if "title" not in metadata.keys():
|
|
21
|
+
metadata["title"] = content.split("#")[1].split("\n")[0]
|
|
22
|
+
|
|
23
|
+
for key, value in metadata.items():
|
|
24
|
+
if key == "title":
|
|
25
|
+
solara.Title(value)
|
|
26
|
+
elif ":" in key:
|
|
27
|
+
solara.Meta(property=key, content=value)
|
|
28
|
+
else:
|
|
29
|
+
solara.Meta(name=key, content=value)
|
|
30
|
+
solara.Markdown(content, unsafe_solara_execute=unsafe_solara_execute)
|
solara/website/pages/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
|
|
3
3
|
import solara
|
|
4
|
+
from solara import autorouting
|
|
4
5
|
from solara.alias import rv
|
|
5
6
|
from solara.components.title import Title
|
|
6
7
|
from solara.server import server
|
|
@@ -13,7 +14,7 @@ title = "Home"
|
|
|
13
14
|
route_order = ["/", "showcase", "documentation", "apps", "contact", "changelog"]
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
_redirects = {
|
|
17
18
|
"/docs": "/documentation/getting_started/introduction",
|
|
18
19
|
"/docs/installing": "/documentation/getting_started/installing",
|
|
19
20
|
"/docs/quickstart": "/documentation/getting_started",
|
|
@@ -111,12 +112,18 @@ server._redirects = {
|
|
|
111
112
|
"/api/use_thread": "/documentation/api/hooks/use_thread",
|
|
112
113
|
"/api/use_trait_observe": "/documentation/api/hooks/use_trait_observe",
|
|
113
114
|
"/examples/fullscreen": "/documentation/examples/fullscreen",
|
|
114
|
-
"/examples/fullscreen/authorization": "/
|
|
115
|
-
"/examples/fullscreen/
|
|
116
|
-
"/examples/fullscreen/
|
|
117
|
-
"/examples/fullscreen/
|
|
118
|
-
"/examples/fullscreen/
|
|
119
|
-
"/examples/fullscreen/
|
|
115
|
+
"/examples/fullscreen/authorization": "/apps/authorization",
|
|
116
|
+
"documentation/examples/fullscreen/authorization": "/apps/authorization",
|
|
117
|
+
"/examples/fullscreen/layout-demo": "/apps/layout-demo",
|
|
118
|
+
"/documentation/examples/fullscreen/layout-demo": "/apps/layout-demo",
|
|
119
|
+
"/examples/fullscreen/multipage": "/apps/multipage",
|
|
120
|
+
"/documentation/examples/fullscreen/multipage": "/apps/multipage",
|
|
121
|
+
"/examples/fullscreen/scatter": "apps/scatter",
|
|
122
|
+
"/documentation/examples/fullscreen/scatter": "/apps/scatter",
|
|
123
|
+
"/examples/fullscreen/scrolling": "/apps/scrolling",
|
|
124
|
+
"/documentation/examples/fullscreen/scrolling": "/apps/scrolling",
|
|
125
|
+
"/examples/fullscreen/tutorial-streamlit": "/apps/tutorial-streamlit",
|
|
126
|
+
"/documentation/examples/fullscreen/tutorial-streamlit": "/apps/tutorial-streamlit",
|
|
120
127
|
"/api/route": "/documentation/api/routing/route",
|
|
121
128
|
"/api/route/kiwi": "/documentation/api/routing/route/kiwi",
|
|
122
129
|
"/api/route/banana": "/documentation/api/routing/route/banana",
|
|
@@ -207,6 +214,10 @@ server._redirects = {
|
|
|
207
214
|
}
|
|
208
215
|
|
|
209
216
|
|
|
217
|
+
server._redirects = _redirects
|
|
218
|
+
autorouting._redirects = _redirects
|
|
219
|
+
|
|
220
|
+
|
|
210
221
|
@solara.component
|
|
211
222
|
def Page():
|
|
212
223
|
solara.Markdown("should not see me")
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Solara Changelog
|
|
2
2
|
|
|
3
|
+
## Version 1.30.0
|
|
4
|
+
|
|
5
|
+
### Details
|
|
6
|
+
|
|
7
|
+
* Feature: Multiple file support for `FileDrop`. [#562](https://github.com/widgetti/solara/pull/562)
|
|
8
|
+
* Feature: Solara server is now compatible with Pyodide (threads are combined if they cannot be used). [#569](https://github.com/widgetti/solara/pull/569)
|
|
9
|
+
* Bug Fix: Nested Vuetify apps were not displayed correctly. [#570](https://github.com/widgetti/solara/pull/570)
|
|
10
|
+
* Bug Fix: `on_kernel_start` callbacks were accumulating on hot reload. [#556](https://github.com/widgetti/solara/pull/556)
|
|
11
|
+
* Bug Fix: ES Modules were being loaded multiple times on hot reload. [#559](https://github.com/widgetti/solara/pull/559)
|
|
12
|
+
* Bug Fix: Theme CSS was not loaded when `rootPath` was non-trivial. [829946c](https://github.com/widgetti/solara/commit/829946c4bf47a2ea78d783bb500faaab93b2549b)
|
|
13
|
+
* Fix: `Route.component` is now rendered, instead of the internal `RenderPage` component. [#555](https://github.com/widgetti/solara/pull/555)
|
|
14
|
+
* Fix: `Route.layout` now allows for layouts to be used when defining routes manually. [#554](https://github.com/widgetti/solara/pull/554)
|
|
15
|
+
* Fix: Allow custom command like arguments that can be parsed by the application. [#558](https://github.com/widgetti/solara/pull/558)
|
|
16
|
+
|
|
17
|
+
|
|
3
18
|
## Version 1.29.1
|
|
4
19
|
|
|
5
20
|
### Details
|
|
@@ -1,36 +1,9 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
import solara
|
|
4
3
|
from solara.autorouting import generate_routes_directory
|
|
4
|
+
from solara.website.components.markdown import MarkdownWithMetadata
|
|
5
5
|
|
|
6
|
-
title = "Advanced"
|
|
7
6
|
HERE = Path(__file__)
|
|
8
7
|
# if we didn't put the content in the subdirectory, but pointed to the current file
|
|
9
8
|
# we would include the current file recursively, causing an infinite loop
|
|
10
|
-
routes = generate_routes_directory(HERE.parent / "content")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@solara.component
|
|
14
|
-
def Page(route_external=None):
|
|
15
|
-
solara.Markdown(Path(HERE.parent / "content" / "10-howto" / "00-overview.md").read_text())
|
|
16
|
-
|
|
17
|
-
with solara.Row(justify="center", style={"flex-wrap": "wrap", "align-items": "start"}):
|
|
18
|
-
for child in route_external.children:
|
|
19
|
-
if child.path == "/":
|
|
20
|
-
continue
|
|
21
|
-
|
|
22
|
-
card_title = solara.Link("/documentation/advanced/" + child.path, children=[child.label])
|
|
23
|
-
|
|
24
|
-
with solara.Card(title=card_title, style={"min-width": "300px"}):
|
|
25
|
-
with solara.v.List():
|
|
26
|
-
with solara.v.ListItemGroup():
|
|
27
|
-
for grandchild in child.children:
|
|
28
|
-
if grandchild.path == "/":
|
|
29
|
-
continue
|
|
30
|
-
with solara.Link(
|
|
31
|
-
"/documentation/advanced/" + child.path + "/" + grandchild.path
|
|
32
|
-
if child.path != "/"
|
|
33
|
-
else "/documentation/advanced/" + grandchild.path
|
|
34
|
-
):
|
|
35
|
-
with solara.v.ListItem():
|
|
36
|
-
solara.v.ListItemTitle(children=[grandchild.label])
|
|
9
|
+
routes = generate_routes_directory(HERE.parent / "content", MarkdownWithMetadata)
|
|
@@ -1 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Overview of how-to articles
|
|
3
|
+
description: The how-tos are meant as deeper dives into specific topics from various perspectives
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
The how-tos are meant as deeper dives into specific topics. Although we try to write each how-to as a standalone document, some parts of a how-to may build on top of a previous one.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Building multi-page apps in Solara
|
|
3
|
+
description: The simplest way to create a multi-page app is to create a directory with multiple scripts.
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Multi-page support
|
|
2
7
|
|
|
3
8
|
In the [Web App tutorial](/documentation/getting_started/tutorials/web-app), we created an application consisting of a single page. Web applications generally have multiple pages, and Solara supports this as well.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Making different layouts in Solara
|
|
3
|
+
description: Solara comes with a layout system ideal for data apps. Learn how to use them in this short guide.
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Layout
|
|
2
7
|
|
|
3
8
|
Solara comes with a layout system ideal for data apps.
|
|
@@ -33,7 +38,7 @@ def Page():
|
|
|
33
38
|
solara.Info("two per column on small screens, three per column on large screens")
|
|
34
39
|
```
|
|
35
40
|
|
|
36
|
-
[Navigate here to watch this layout in a full browser window](/documentation/examples/fullscreen/
|
|
41
|
+
[Navigate here to watch this layout in a full browser window](/documentation/examples/fullscreen/layout-demo)
|
|
37
42
|
|
|
38
43
|
The key takeaways are:
|
|
39
44
|
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Embedding Solara applications into existing websites
|
|
3
|
+
description: Solara can be embedded into existing websites. Although it is technically possible to embed a Solara app into an existing webpage,
|
|
4
|
+
we currently support embedding primarily via iframes.
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# Embedding in existing websites
|
|
2
8
|
|
|
3
9
|
Solara can be embedded into existing websites. Although it is technically possible to embed a Solara app into an existing webpage, we currently support embedding primarily via iframes.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Using various ipywidgets libraries within a Solara application
|
|
3
|
+
description: Solara can work with virtually any ipywidget library, and enables powerful interactivity with libraries like ipyleaflet, ipydatagrid, and bqplot.
|
|
4
|
+
---
|
|
1
5
|
# How can I use ipywidget library X?
|
|
2
6
|
|
|
3
7
|
Solara can work with any ipywidget library, such as [ipyleaflet](https://github.com/jupyter-widgets/ipyleaflet), [ipydatagrid](https://github.com/bloomberg/ipydatagrid) or [bqplot](https://github.com/bqplot/bqplot).
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Understanding Solara's functionality
|
|
3
|
+
description: Solara builds on existing technologies. If you are new to Solara or some of the underlying technologies, you may feel lost at times.
|
|
4
|
+
These 'understanding' guides are meant to help you understand topics at a deeper level and how they connect to Solara.
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# Introduction
|
|
2
8
|
|
|
3
9
|
Solara builds on existing technologies. If you are new to Solara or some of the underlying technologies, you may feel lost at times.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Understanding how ipywidgets work together with Solara
|
|
3
|
+
description: Solara's primarily standalone app approach differs from the ipywidgets one. This article dives deeper into the different approaches and adapting to using
|
|
4
|
+
ipywidgets within Solara.
|
|
5
|
+
---
|
|
1
6
|
# IPywidgets
|
|
2
7
|
|
|
3
8
|
* [Documentation](https://ipywidgets.readthedocs.io/en/stable/)
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Using ipyvuetify widgets with Solara
|
|
3
|
+
description: Ipyvuetify is an ipywidgets based library that wraps the Vuetify javascript framework for use with python widgets. Learn how to use these to build apps with Solara.
|
|
4
|
+
---
|
|
1
5
|
# ipyvuetify
|
|
2
6
|
|
|
3
7
|
* [Documentation](https://ipyvuetify.readthedocs.io/)
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Using Solara with containers to build complex UIs
|
|
3
|
+
description: Solara allows for using python with statements to populate your UIs in a structured manner. This makes it easy to compose different components to build cohesive
|
|
4
|
+
wholes.
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# Laying out components with containers
|
|
2
8
|
|
|
3
9
|
## Introduction
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Understanding different parts of Solara
|
|
3
|
+
description: Solara is made of two main components, the bulk of the Solara package with UI elements that can be used together with Jupyter, and Solara server, which
|
|
4
|
+
allows for these UI elements to be used in standalone apps and dashboards
|
|
5
|
+
---
|
|
1
6
|
# Solara
|
|
2
7
|
|
|
3
8
|
Solara combines [ipywidgets](./ipywidgets), [reacton](./reacton) and puts it into a opinionated framework to make web apps with a focus on data (data apps for short).
|