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.
Files changed (70) hide show
  1. solara/__init__.py +1 -1
  2. solara/autorouting.py +58 -67
  3. solara/components/component_vue.py +8 -7
  4. solara/components/image.py +1 -1
  5. solara/scope/__init__.py +3 -2
  6. solara/server/kernel_context.py +3 -0
  7. solara/server/static/solara_bootstrap.py +1 -1
  8. solara/website/components/markdown.py +30 -0
  9. solara/website/pages/__init__.py +18 -7
  10. solara/website/pages/changelog/changelog.md +15 -0
  11. solara/website/pages/documentation/advanced/__init__.py +2 -29
  12. solara/website/pages/documentation/advanced/content/10-howto/00-overview.md +5 -0
  13. solara/website/pages/documentation/advanced/content/10-howto/10-multipage.md +5 -0
  14. solara/website/pages/documentation/advanced/content/10-howto/20-layout.md +6 -1
  15. solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +5 -0
  16. solara/website/pages/documentation/advanced/content/10-howto/31-debugging.md +4 -1
  17. solara/website/pages/documentation/advanced/content/10-howto/40-embed.md +6 -0
  18. solara/website/pages/documentation/advanced/content/10-howto/50-ipywidget_libraries.md +4 -0
  19. solara/website/pages/documentation/advanced/content/20-understanding/00-introduction.md +6 -0
  20. solara/website/pages/documentation/advanced/content/20-understanding/05-ipywidgets.md +5 -0
  21. solara/website/pages/documentation/advanced/content/20-understanding/06-ipyvuetify.md +4 -0
  22. solara/website/pages/documentation/advanced/content/20-understanding/10-reacton.md +4 -0
  23. solara/website/pages/documentation/advanced/content/20-understanding/12-reacton-basics.md +4 -0
  24. solara/website/pages/documentation/advanced/content/20-understanding/15-anatomy.md +4 -0
  25. solara/website/pages/documentation/advanced/content/20-understanding/17-rules-of-hooks.md +4 -0
  26. solara/website/pages/documentation/advanced/content/20-understanding/18-containers.md +6 -0
  27. solara/website/pages/documentation/advanced/content/20-understanding/20-solara.md +5 -0
  28. solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +4 -0
  29. solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +5 -0
  30. solara/website/pages/documentation/advanced/content/20-understanding/60-voila.md +5 -0
  31. solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +4 -0
  32. solara/website/pages/documentation/advanced/content/40-development/01-contribute.md +4 -0
  33. solara/website/pages/documentation/advanced/content/40-development/10-setup.md +4 -0
  34. solara/website/pages/documentation/examples/general/custom_storage.py +1 -1
  35. solara/website/pages/documentation/faq/__init__.py +2 -1
  36. solara/website/pages/documentation/faq/content/99-faq.md +4 -1
  37. solara/website/pages/documentation/getting_started/__init__.py +2 -8
  38. solara/website/pages/documentation/getting_started/content/00-quickstart.md +5 -0
  39. solara/website/pages/documentation/getting_started/content/01-introduction.md +4 -0
  40. solara/website/pages/documentation/getting_started/content/02-installing.md +4 -0
  41. solara/website/pages/documentation/getting_started/content/04-tutorials/00-overview.md +5 -0
  42. solara/website/pages/documentation/getting_started/content/04-tutorials/20-web-app.md +5 -0
  43. solara/website/pages/documentation/getting_started/content/04-tutorials/30-ipywidgets.md +4 -0
  44. solara/website/pages/documentation/getting_started/content/04-tutorials/40-streamlit.md +6 -1
  45. solara/website/pages/documentation/getting_started/content/04-tutorials/50-dash.md +4 -0
  46. solara/website/pages/documentation/getting_started/content/05-fundamentals/00-overview.md +4 -0
  47. solara/website/pages/documentation/getting_started/content/05-fundamentals/10-components.md +5 -0
  48. solara/website/pages/documentation/getting_started/content/05-fundamentals/50-state-management.md +5 -0
  49. solara/website/pages/documentation/getting_started/content/06-reference/40-static_files.md +4 -0
  50. solara/website/pages/documentation/getting_started/content/06-reference/41-asset-files.md +4 -0
  51. solara/website/pages/documentation/getting_started/content/06-reference/60-static-site-generation.md +4 -0
  52. solara/website/pages/documentation/getting_started/content/06-reference/70-search.md +4 -0
  53. solara/website/pages/documentation/getting_started/content/06-reference/80-reloading.md +4 -0
  54. solara/website/pages/documentation/getting_started/content/06-reference/90-notebook-support.md +4 -1
  55. solara/website/pages/documentation/getting_started/content/06-reference/95-caching.md +5 -0
  56. solara/website/pages/documentation/getting_started/content/07-deploying/00-overview.md +4 -0
  57. solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +4 -0
  58. solara/website/pages/documentation/getting_started/content/07-deploying/20-cloud-hosted.md +4 -0
  59. solara/website/pages/documentation/getting_started/content/80-what-is-lab.md +7 -0
  60. solara/website/pages/documentation/getting_started/content/90-troubleshoot.md +4 -0
  61. {solara-1.30.0.dist-info → solara-1.30.1.dist-info}/METADATA +2 -2
  62. {solara-1.30.0.dist-info → solara-1.30.1.dist-info}/RECORD +67 -68
  63. solara/website/pages/documentation/advanced/content/__init__.py +0 -0
  64. solara/website/pages/documentation/getting_started/content/08-lab/00-what-is-lab.md +0 -3
  65. solara/website/pages/documentation/getting_started/content/__init__.py +0 -0
  66. {solara-1.30.0.data → solara-1.30.1.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
  67. {solara-1.30.0.data → solara-1.30.1.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
  68. {solara-1.30.0.dist-info → solara-1.30.1.dist-info}/WHEEL +0 -0
  69. {solara-1.30.0.dist-info → solara-1.30.1.dist-info}/entry_points.txt +0 -0
  70. {solara-1.30.0.dist-info → solara-1.30.1.dist-info}/licenses/LICENSE +0 -0
solara/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """Build webapps using IPywidgets"""
2
- __version__ = "1.30.0"
2
+ __version__ = "1.30.1"
3
3
  github_url = "https://github.com/widgetti/solara"
4
4
  git_branch = "master"
5
5
 
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=set_path)
96
- solara.routing._location_context.provide(solara.routing._Location(path, set_path))
97
- solara.routing.router_context.provide(solara.routing.Router(path, routes=routes, set_path=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(level_max + 1):
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
- # used in for example the docs for use_route, only the route path are specified,
160
- # nothing else, which means we will not follow that route path, but limit it to rendering
161
- # router.path_routes[level_max] instead. It's assumed that component (Page) will continue
162
- # handling the rest of the routing
163
- if route.data is None and route.module is None and route.component is None:
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
- if route_current.data is None and route_current.module is None and route_current.component is None:
172
- return solara.Error(f"Page not found: {router.path}, route does not link to a path or module or component")
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
- route = solara.Route(
413
- name, component=get_page(submod, required=False), children=generate_routes(submod), module=submod, layout=None, label=title
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
- children = getattr(submod, "routes", [])
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
- layout = getattr(module, "Layout", None)
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
- title = get_title(module)
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
- if name.startswith("on_") and name[3:] in parameters:
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
- trait = traitlets.Any(default_value=param.default)
39
- classprops[name] = trait.tag(sync=True, **widgets.widget_serialization)
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
 
@@ -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/scopes) for an example.
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/scopes) for an example.
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
 
@@ -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.0-py2.py3-none-any.whl", keep_going=True)
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)
@@ -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
- server._redirects = {
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": "/documentation/examples/fullscreen/authorization",
115
- "/examples/fullscreen/layout_demo": "/documentation/examples/fullscreen/layout_demo",
116
- "/examples/fullscreen/multipage": "/documentation/examples/fullscreen/multipage",
117
- "/examples/fullscreen/scatter": "/documentation/examples/fullscreen/scatter",
118
- "/examples/fullscreen/scrolling": "/documentation/examples/fullscreen/scrolling",
119
- "/examples/fullscreen/tutorial_streamlit": "/documentation/examples/fullscreen/tutorial_streamlit",
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/layout_demo)
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,8 @@
1
+ ---
2
+ title: Testing your Solara application, both front and back end
3
+ description: Using solara you can test both the front and back end functionalities of your application.
4
+ ---
5
+
1
6
  # Testing with Solara
2
7
  # Testing Application Logic
3
8
 
@@ -1,4 +1,7 @@
1
-
1
+ ---
2
+ title: Debugging Solara applications
3
+ description: You can use the Python debugger to debug your Solara app.
4
+ ---
2
5
  # Debugging
3
6
 
4
7
  ## PDB
@@ -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,7 @@
1
+ ---
2
+ title: Understanding how Solara builds on Reacton
3
+ description: Solara adapts the React way of using components declaratively to build UIs and brings it to Python.
4
+ ---
1
5
  # Reacton
2
6
 
3
7
  * [Documentation](https://reacton.solara.dev/)
@@ -1,3 +1,7 @@
1
+ ---
2
+ title: Understanding the basics of Reacton
3
+ description: Using your favorite component, we try to understand better how it works to improve your understanding of Reacton and, thus, how to use Solara.
4
+ ---
1
5
  # Reacton basic understanding
2
6
 
3
7
 
@@ -1,3 +1,7 @@
1
+ ---
2
+ title: The anatomy of Solara's functionality
3
+ description: Dive deep into how Solara uses the technologies it builds on.
4
+ ---
1
5
  # Anatomy
2
6
 
3
7
  For communication, it is useful to speak the same language and use the same idiom.
@@ -1,3 +1,7 @@
1
+ ---
2
+ title: The rules of hooks in Solara
3
+ description: Learn the rules that govern the usage of hooks in your Solara application.
4
+ ----
1
5
  # Rules of hooks
2
6
 
3
7
  For now, we refer to the [ReactJS documentation](https://reactjs.org/docs/hooks-rules.html).
@@ -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).