solara-ui 1.42.0__py2.py3-none-any.whl → 1.43.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. solara/__init__.py +1 -1
  2. solara/__main__.py +10 -5
  3. solara/_stores.py +14 -10
  4. solara/components/__init__.py +18 -1
  5. solara/components/datatable.py +4 -4
  6. solara/components/input.py +5 -1
  7. solara/components/markdown.py +46 -10
  8. solara/components/misc.py +2 -2
  9. solara/lab/components/__init__.py +1 -0
  10. solara/lab/components/chat.py +3 -3
  11. solara/lab/components/input_time.py +133 -0
  12. solara/lab/hooks/dataframe.py +1 -0
  13. solara/lab/utils/dataframe.py +11 -1
  14. solara/server/app.py +63 -30
  15. solara/server/flask.py +12 -2
  16. solara/server/jupyter/server_extension.py +1 -0
  17. solara/server/kernel.py +50 -3
  18. solara/server/kernel_context.py +66 -7
  19. solara/server/patch.py +25 -29
  20. solara/server/server.py +15 -5
  21. solara/server/settings.py +11 -0
  22. solara/server/shell.py +19 -1
  23. solara/server/starlette.py +37 -9
  24. solara/server/static/solara_bootstrap.py +1 -1
  25. solara/settings.py +3 -0
  26. solara/tasks.py +18 -8
  27. solara/test/pytest_plugin.py +1 -0
  28. solara/toestand.py +33 -2
  29. solara/util.py +18 -0
  30. solara/website/components/docs.py +4 -0
  31. solara/website/components/markdown.py +17 -3
  32. solara/website/pages/changelog/changelog.md +9 -1
  33. solara/website/pages/documentation/advanced/content/20-understanding/50-solara-server.md +10 -0
  34. solara/website/pages/documentation/api/routing/route.py +10 -12
  35. solara/website/pages/documentation/api/routing/use_route.py +26 -30
  36. solara/website/pages/documentation/components/advanced/link.py +6 -8
  37. solara/website/pages/documentation/components/advanced/meta.py +6 -9
  38. solara/website/pages/documentation/components/advanced/style.py +7 -9
  39. solara/website/pages/documentation/components/input/file_browser.py +12 -14
  40. solara/website/pages/documentation/components/lab/input_time.py +15 -0
  41. solara/website/pages/documentation/components/layout/columns_responsive.py +37 -39
  42. solara/website/pages/documentation/components/layout/gridfixed.py +4 -6
  43. solara/website/pages/documentation/components/output/html.py +1 -3
  44. solara/website/pages/documentation/components/page/head.py +4 -7
  45. solara/website/pages/documentation/examples/__init__.py +9 -0
  46. solara/website/pages/documentation/examples/ai/chatbot.py +60 -44
  47. solara/website/pages/documentation/examples/general/live_update.py +1 -0
  48. solara/website/pages/documentation/faq/content/99-faq.md +9 -0
  49. solara/website/pages/documentation/getting_started/content/00-quickstart.md +1 -1
  50. solara/website/pages/documentation/getting_started/content/04-tutorials/_jupyter_dashboard_1.ipynb +23 -19
  51. solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +2 -2
  52. solara/website/pages/roadmap/roadmap.md +3 -0
  53. {solara_ui-1.42.0.dist-info → solara_ui-1.43.0.dist-info}/METADATA +2 -2
  54. {solara_ui-1.42.0.dist-info → solara_ui-1.43.0.dist-info}/RECORD +58 -56
  55. {solara_ui-1.42.0.data → solara_ui-1.43.0.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
  56. {solara_ui-1.42.0.data → solara_ui-1.43.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
  57. {solara_ui-1.42.0.dist-info → solara_ui-1.43.0.dist-info}/WHEEL +0 -0
  58. {solara_ui-1.42.0.dist-info → solara_ui-1.43.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,6 +15,7 @@ import anyio
15
15
  import starlette.websockets
16
16
  import uvicorn.server
17
17
  import websockets.legacy.http
18
+ import websockets.exceptions
18
19
 
19
20
  from solara.server.utils import path_is_child_of
20
21
 
@@ -121,37 +122,55 @@ class WebsocketWrapper(websocket.WebsocketWrapper):
121
122
  while len(self.to_send) > 0:
122
123
  first = self.to_send.pop(0)
123
124
  if isinstance(first, bytes):
124
- await self.ws.send_bytes(first)
125
+ await self._send_bytes_exc(first)
125
126
  else:
126
- await self.ws.send_text(first)
127
+ await self._send_text_exc(first)
128
+
129
+ async def _send_bytes_exc(self, data: bytes):
130
+ # make sures we catch the starlette/websockets specific exception
131
+ # and re-raise it as a websocket.WebSocketDisconnect
132
+ try:
133
+ await self.ws.send_bytes(data)
134
+ except (websockets.exceptions.ConnectionClosed, starlette.websockets.WebSocketDisconnect, RuntimeError) as e:
135
+ # starlette throws a RuntimeError once you call send after the connection is closed
136
+ raise websocket.WebSocketDisconnect() from e
137
+
138
+ async def _send_text_exc(self, data: str):
139
+ # make sures we catch the starlette/websockets specific exception
140
+ # and re-raise it as a websocket.WebSocketDisconnect
141
+ try:
142
+ await self.ws.send_text(data)
143
+ except (websockets.exceptions.ConnectionClosed, starlette.websockets.WebSocketDisconnect, RuntimeError) as e:
144
+ # starlette throws a RuntimeError once you call send after the connection is closed
145
+ raise websocket.WebSocketDisconnect() from e
127
146
 
128
147
  def close(self):
129
148
  if self.portal is None:
130
149
  asyncio.ensure_future(self.ws.close())
131
150
  else:
132
- self.portal.call(self.ws.close) # type: ignore
151
+ self.portal.call(self.ws.close)
133
152
 
134
153
  def send_text(self, data: str) -> None:
135
154
  if self.portal is None:
136
- task = self.event_loop.create_task(self.ws.send_text(data))
155
+ task = self.event_loop.create_task(self._send_text_exc(data))
137
156
  self.tasks.add(task)
138
157
  task.add_done_callback(self.tasks.discard)
139
158
  else:
140
159
  if settings.main.experimental_performance:
141
160
  self.to_send.append(data)
142
161
  else:
143
- self.portal.call(self.ws.send_bytes, data) # type: ignore
162
+ self.portal.call(self._send_text_exc, data)
144
163
 
145
164
  def send_bytes(self, data: bytes) -> None:
146
165
  if self.portal is None:
147
- task = self.event_loop.create_task(self.ws.send_bytes(data))
166
+ task = self.event_loop.create_task(self._send_bytes_exc(data))
148
167
  self.tasks.add(task)
149
168
  task.add_done_callback(self.tasks.discard)
150
169
  else:
151
170
  if settings.main.experimental_performance:
152
171
  self.to_send.append(data)
153
172
  else:
154
- self.portal.call(self.ws.send_bytes, data) # type: ignore
173
+ self.portal.call(self._send_bytes_exc, data)
155
174
 
156
175
  async def receive(self):
157
176
  if self.portal is None:
@@ -159,9 +178,9 @@ class WebsocketWrapper(websocket.WebsocketWrapper):
159
178
  else:
160
179
  if hasattr(self.portal, "start_task_soon"):
161
180
  # version 3+
162
- fut = self.portal.start_task_soon(self.ws.receive) # type: ignore
181
+ fut = self.portal.start_task_soon(self.ws.receive)
163
182
  else:
164
- fut = self.portal.spawn_task(self.ws.receive) # type: ignore
183
+ fut = self.portal.spawn_task(self.ws.receive)
165
184
 
166
185
  message = await asyncio.wrap_future(fut)
167
186
  if "text" in message:
@@ -444,6 +463,7 @@ See also https://solara.dev/documentation/getting_started/deploying/self-hosted
444
463
  session_id = request.cookies.get(server.COOKIE_KEY_SESSION_ID) or str(uuid4())
445
464
  samesite = "lax"
446
465
  secure = False
466
+ httponly = settings.session.http_only
447
467
  # we want samesite, so we can set a cookie when embedded in an iframe, such as on huggingface
448
468
  # however, samesite=none requires Secure https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
449
469
  # when hosted on the localhost domain we can always set the Secure flag
@@ -469,6 +489,7 @@ Also check out the following Solara documentation:
469
489
  expires="Fri, 01 Jan 2038 00:00:00 GMT",
470
490
  samesite=samesite, # type: ignore
471
491
  secure=secure, # type: ignore
492
+ httponly=httponly, # type: ignore
472
493
  ) # type: ignore
473
494
  return response
474
495
 
@@ -549,12 +570,19 @@ class StaticCdn(StaticFilesOptionalAuth):
549
570
 
550
571
 
551
572
  def on_startup():
573
+ appmod.ensure_apps_initialized()
552
574
  # TODO: configure and set max number of threads
553
575
  # see https://github.com/encode/starlette/issues/1724
554
576
  telemetry.server_start()
555
577
 
556
578
 
557
579
  def on_shutdown():
580
+ # shutdown all kernels
581
+ for context in list(kernel_context.contexts.values()):
582
+ try:
583
+ context.close()
584
+ except: # noqa
585
+ logger.exception("error closing kernel on shutdown")
558
586
  telemetry.server_stop()
559
587
 
560
588
 
@@ -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.42.0-py2.py3-none-any.whl", keep_going=True)
122
+ await micropip.install("/wheels/solara-1.43.0-py2.py3-none-any.whl", keep_going=True)
123
123
  import solara
124
124
 
125
125
  el = solara.Warning("lala")
solara/settings.py CHANGED
@@ -54,6 +54,9 @@ class Assets(BaseSettings):
54
54
 
55
55
  class MainSettings(BaseSettings):
56
56
  check_hooks: str = "warn"
57
+ allow_reactive_boolean: bool = True
58
+ # TODO: also change default_container in solara/components/__init__.py
59
+ default_container: Optional[str] = "Column"
57
60
 
58
61
  class Config:
59
62
  env_prefix = "solara_"
solara/tasks.py CHANGED
@@ -1,3 +1,4 @@
1
+ import sys
1
2
  import abc
2
3
  import asyncio
3
4
  import dataclasses
@@ -27,6 +28,12 @@ from solara.toestand import Singleton
27
28
 
28
29
  from .toestand import Ref as ref
29
30
 
31
+ if sys.version_info >= (3, 8):
32
+ from typing import Literal
33
+ else:
34
+ from typing_extensions import Literal
35
+
36
+
30
37
  R = TypeVar("R")
31
38
  T = TypeVar("T")
32
39
  P = typing_extensions.ParamSpec("P")
@@ -686,24 +693,27 @@ def task(
686
693
  return wrapper(f)
687
694
 
688
695
 
696
+ # Quotes around Task[...] are needed in Python <= 3.9, since ParamSpec doesn't properly support non-type arguments
697
+ # i.e. [] is taken as a value instead of a type
698
+ # See https://github.com/python/typing_extensions/issues/126 and related issues
689
699
  @overload
690
700
  def use_task(
691
701
  f: None = None,
692
702
  *,
693
- dependencies: None = ...,
703
+ dependencies: Literal[None] = ...,
694
704
  raise_error=...,
695
705
  prefer_threaded=...,
696
- ) -> Callable[[Callable[P, R]], Task[P, R]]: ...
706
+ ) -> Callable[[Callable[[], R]], "Task[[], R]"]: ...
697
707
 
698
708
 
699
709
  @overload
700
710
  def use_task(
701
- f: Callable[P, R],
711
+ f: Callable[[], R],
702
712
  *,
703
- dependencies: None = ...,
713
+ dependencies: Literal[None] = ...,
704
714
  raise_error=...,
705
715
  prefer_threaded=...,
706
- ) -> Task[P, R]: ...
716
+ ) -> "Task[[], R]": ...
707
717
 
708
718
 
709
719
  @overload
@@ -727,12 +737,12 @@ def use_task(
727
737
 
728
738
 
729
739
  def use_task(
730
- f: Union[None, Callable[P, R]] = None,
740
+ f: Union[None, Callable[[], R]] = None,
731
741
  *,
732
742
  dependencies: Union[None, List] = [],
733
743
  raise_error=True,
734
744
  prefer_threaded=True,
735
- ) -> Union[Callable[[Callable[P, R]], Task[P, R]], Task[P, R]]:
745
+ ) -> Union[Callable[[Callable[[], R]], "Task[[], R]"], "Task[[], R]"]:
736
746
  """A hook that runs a function or coroutine function as a task and returns the result.
737
747
 
738
748
  Allows you to run code in the background, with the UI available to the user. This is useful for long running tasks,
@@ -811,7 +821,7 @@ def use_task(
811
821
  """
812
822
 
813
823
  def wrapper(f):
814
- def create_task() -> Task[P, R]:
824
+ def create_task() -> "Task[[], R]":
815
825
  return task(f, prefer_threaded=prefer_threaded)
816
826
 
817
827
  task_instance = solara.use_memo(create_task, dependencies=[])
@@ -150,6 +150,7 @@ def solara_app(solara_server):
150
150
  solara.server.app.apps["__default__"].close()
151
151
  if isinstance(app, str):
152
152
  app = solara.server.app.AppScript(app)
153
+ app.init()
153
154
  used_app = app
154
155
  solara.server.app.apps["__default__"] = app
155
156
  try:
solara/toestand.py CHANGED
@@ -98,6 +98,37 @@ class ValueBase(Generic[T]):
98
98
  self.listeners: Dict[str, Set[Tuple[Callable[[T], None], Optional[ContextManager]]]] = defaultdict(set)
99
99
  self.listeners2: Dict[str, Set[Tuple[Callable[[T, T], None], Optional[ContextManager]]]] = defaultdict(set)
100
100
 
101
+ # make sure all boolean operations give type errors
102
+ if not solara.settings.main.allow_reactive_boolean:
103
+
104
+ def __bool__(self):
105
+ raise TypeError("Reactive vars are not allowed in boolean expressions, did you mean to use .value?")
106
+
107
+ def __eq__(self, other):
108
+ raise TypeError(f"'==' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
109
+
110
+ def __ne__(self, other):
111
+ raise TypeError(f"'!=' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
112
+
113
+ # If we explicitly define __eq__, we need to explicitly define __hash__ as well
114
+ # Otherwise our class is marked unhashable
115
+ __hash__ = object.__hash__
116
+
117
+ def __lt__(self, other):
118
+ raise TypeError(f"'<' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
119
+
120
+ def __le__(self, other):
121
+ raise TypeError(f"'<=' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
122
+
123
+ def __gt__(self, other):
124
+ raise TypeError(f"'>' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
125
+
126
+ def __ge__(self, other):
127
+ raise TypeError(f"'>=' not supported between a Reactive and {other.__class__.__name__}, did you mean to use .value?")
128
+
129
+ def __len__(self):
130
+ raise TypeError("'len(...)' is not supported for a Reactive, did you mean to use .value?")
131
+
101
132
  @property
102
133
  def lock(self):
103
134
  raise NotImplementedError
@@ -410,10 +441,10 @@ class KernelStoreFactory(KernelStore[S]):
410
441
 
411
442
  def mutation_detection_storage(default_value: S, key=None, equals=None) -> ValueBase[S]:
412
443
  from solara.util import equals_pickle as default_equals
413
- from ._stores import MutateDetectorStore, StoreValue, _PublicValueNotSet
444
+ from ._stores import MutateDetectorStore, StoreValue, _PublicValueNotSet, _SetValueNotSet
414
445
 
415
446
  kernel_store = KernelStoreValue[StoreValue[S]](
416
- StoreValue[S](private=default_value, public=_PublicValueNotSet(), get_traceback=None, set_value=None, set_traceback=None),
447
+ StoreValue[S](private=default_value, public=_PublicValueNotSet(), get_traceback=None, set_value=_SetValueNotSet(), set_traceback=None),
417
448
  key=key,
418
449
  unwrap=lambda x: x.private,
419
450
  )
solara/util.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import base64
2
2
  import contextlib
3
+ import functools
3
4
  import gzip
4
5
  import hashlib
5
6
  import json
@@ -328,3 +329,20 @@ def is_running_in_vscode():
328
329
 
329
330
  def is_running_in_voila():
330
331
  return os.environ.get("SERVER_SOFTWARE", "").startswith("voila")
332
+
333
+
334
+ def once(f):
335
+ called = False
336
+ return_value = None
337
+
338
+ @functools.wraps(f)
339
+ def wrapper():
340
+ nonlocal called
341
+ nonlocal return_value
342
+ if called:
343
+ return return_value
344
+ called = True
345
+ return_value = f()
346
+ return return_value
347
+
348
+ return wrapper
@@ -5,6 +5,8 @@ from .breadcrumbs import BreadCrumbs
5
5
 
6
6
  @solara.component
7
7
  def Gallery(route_external=None):
8
+ from ..pages.documentation.examples import pycafe_projects
9
+
8
10
  if route_external is not None:
9
11
  route_current = route_external
10
12
  else:
@@ -49,6 +51,8 @@ def Gallery(route_external=None):
49
51
  image_url = "https://dxhl76zpt6fap.cloudfront.net/public/api/" + child.path + ".gif"
50
52
  elif child.path in ["card", "dataframe", "pivot_table", "slider"]:
51
53
  image_url = "https://dxhl76zpt6fap.cloudfront.net/public/api/" + child.path + ".png"
54
+ elif child.path in pycafe_projects:
55
+ image_url = f"https://py.cafe/preview/solara/{child.path}"
52
56
  else:
53
57
  image_url = "https://dxhl76zpt6fap.cloudfront.net/public/logo.svg"
54
58
 
@@ -1,4 +1,4 @@
1
- from typing import Dict, List, Union
1
+ from typing import Callable, Dict, List, Union, cast
2
2
 
3
3
  import yaml
4
4
  import markdown
@@ -12,6 +12,7 @@ from solara.components.markdown import formatter, _no_deep_copy_emojione
12
12
  # We want to separate metadata from the markdown files before rendering them, which solara.Markdown doesn't support
13
13
  @solara.component
14
14
  def MarkdownWithMetadata(content: str, unsafe_solara_execute=True):
15
+ cleanups = solara.use_ref(cast(List[Callable[[], None]], []))
15
16
  if "---" in content:
16
17
  pre_content, raw_metadata, post_content = content.split("---")
17
18
  metadata: Dict[str, Union[str, List[str]]] = yaml.safe_load(raw_metadata)
@@ -56,13 +57,17 @@ def MarkdownWithMetadata(content: str, unsafe_solara_execute=True):
56
57
  "name": "solara",
57
58
  "class": "",
58
59
  "validator": mkdocs_pycafe.validator,
59
- "format": mkdocs_pycafe.formatter(type="solara", next_formatter=formatter(unsafe_solara_execute), inside_last_div=False),
60
+ "format": mkdocs_pycafe.formatter(
61
+ type="solara", next_formatter=formatter(unsafe_solara_execute, cleanups.current), inside_last_div=False
62
+ ),
60
63
  },
61
64
  {
62
65
  "name": "python",
63
66
  "class": "highlight",
64
67
  "validator": mkdocs_pycafe.validator,
65
- "format": mkdocs_pycafe.formatter(type="solara", next_formatter=formatter(unsafe_solara_execute), inside_last_div=False),
68
+ "format": mkdocs_pycafe.formatter(
69
+ type="solara", next_formatter=formatter(unsafe_solara_execute, cleanups.current), inside_last_div=False
70
+ ),
66
71
  },
67
72
  ],
68
73
  },
@@ -71,6 +76,15 @@ def MarkdownWithMetadata(content: str, unsafe_solara_execute=True):
71
76
 
72
77
  md_parser = solara.use_memo(make_markdown_object, dependencies=[unsafe_solara_execute])
73
78
 
79
+ def cleanup_wrapper():
80
+ def cleanup():
81
+ for cleanup in cleanups.current:
82
+ cleanup()
83
+
84
+ return cleanup
85
+
86
+ solara.use_effect(cleanup_wrapper, [])
87
+
74
88
  with solara.v.Html(
75
89
  tag="div",
76
90
  style_="display: flex; flex-direction: row; justify-content: center; gap: 15px; max-width: 90%; margin: 0 auto;",
@@ -1,6 +1,6 @@
1
1
  # Solara Changelog
2
2
 
3
- ## Version 1.41.0
3
+ ## Version 1.42.0
4
4
  * Feature: Mutation detection is now available under the `SOLARA_STORAGE_MUTATION_DETECTION` environmental variable. [#595](https://github.com/widgetti/solara/pull/595).
5
5
  * Feature: Autofocusing text inputs is now supported. [#788](https://github.com/widgetti/solara/pull/788).
6
6
  * Feature: Custom colours are now supported for the Solara loading spinner. [#858](https://github.com/widgetti/solara/pull/858)
@@ -9,6 +9,14 @@
9
9
  * Bug Fix: Solara apps running in qt mode (`--qt`) should now always work correctly. [#856](https://github.com/widgetti/solara/pull/856).
10
10
  * Bug Fix: Hot reloading of files outside working directory would crash app. [069a205](https://github.com/widgetti/solara/commit/069a205c88a8cbcb0b0ca23f4d56889c8ad6134a) and [#869](https://github.com/widgetti/solara/pull/869).
11
11
 
12
+ ## Version 1.41.0
13
+ * Feature: Support automatic resizing of Altair (Vega-Lite) figures. [#833](https://github.com/widgetti/solara/pull/833).
14
+ * Feature (Experimental): Support running Solara applications as standalone QT apps. [#835](https://github.com/widgetti/solara/pull/835).
15
+ * Feature: Add option to hide "This website runs on Solara"-banner. [#836](https://github.com/widgetti/solara/pull/836).
16
+ * Feature: Support navigating to hashes. [#814](https://github.com/widgetti/solara/pull/814).
17
+ * Bug Fix: Chunks and assets in nbextensions would fail to load. [9efe26c](https://github.com/widgetti/solara/commit/9efe26cbe00210163a6e8ef251ebfe50ca87fce2).
18
+ * Bug Fix: Vue widget decorator now always uses absolute paths. [#826](https://github.com/widgetti/solara/pull/826).
19
+
12
20
  ## Version 1.40.0
13
21
  * Feature: In Jupyter Notebook and Lab, Solara (server) now renders the [ipypopout](https://github.com/widgetti/ipypopout) window instead of Voila [#805](render ipypopout content in jupyter notebook and lab)
14
22
  * Feature: Support styling input field of [ChatInput component](https://solara.dev/documentation/components/lab/chat). [#800](https://github.com/widgetti/solara/pull/800).
@@ -51,6 +51,7 @@ browser pages. This can be used to store state that outlives a page refresh.
51
51
  We recommend storing the state in an external database, especially in the case of multiple workers/nodes. If you want to store state associated to a session in-memory, make sure to set up sticky sessions.
52
52
 
53
53
 
54
+ The `solara-session-id` cookie is accessible in the browser using JavaScript. If you deem this a security risk, you can disable the cookie by setting the `SOLARA_SESSION_HTTP_ONLY` environment variable to `True`.
54
55
 
55
56
 
56
57
  ## Readiness check
@@ -78,7 +79,16 @@ $ curl http://localhost:8765/resourcez\?verbose
78
79
 
79
80
  The JSON format may be subject to change.
80
81
 
82
+ ## Ignoring notebook extensions
81
83
 
84
+ Not all (classic) jupyter notebook extensions are compatible with Solara, and there is not way to distinguish between notebook extensions that are needed for widgets and those that are not.
85
+ To ignore notebook extensions, you can set the `SOLARA_SERVER_IGNORE_NBEXTENSIONS` environment variable. This is a comma separated list of notebook extensions to ignore. For example, to ignore the `dash/main` and `foo/bar` extensions, you can run:
86
+
87
+ ```bash
88
+ $ SOLARA_SERVER_IGNORE_NBEXTENSIONS="dash/main,foo/bar" solara run nogit/sol.py -a
89
+ ```
90
+
91
+ Note that these error are not fatal, and the Solara app will still run.
82
92
 
83
93
  ## Production mode
84
94
 
@@ -14,18 +14,16 @@ routes = [
14
14
  @solara.component
15
15
  def Page():
16
16
  route_current, routes = solara.use_route()
17
- with solara.VBox() as main:
18
- # solara.Warning("Note the address bar in the browser. It should change to the path of the link.")
19
- solara.Markdown("*Click on one of the links below to change the route and see the url in your browser change, and match the route.*")
20
- with solara.VBox():
21
- for route in routes:
22
- with solara.Link(route):
23
- current = route_current is route
24
- if current:
25
- solara.Success(f"You are at solara.Route(path={route.path!r})")
26
- else:
27
- solara.Info(f"Go to solara.Route(path={route.path!r})")
28
- return main
17
+
18
+ solara.Markdown("*Click on one of the links below to change the route and see the url in your browser change, and match the route.*")
19
+ with solara.VBox():
20
+ for route in routes:
21
+ with solara.Link(route):
22
+ current = route_current is route
23
+ if current:
24
+ solara.Success(f"You are at solara.Route(path={route.path!r})")
25
+ else:
26
+ solara.Info(f"Go to solara.Route(path={route.path!r})")
29
27
 
30
28
 
31
29
  __doc__ += apidoc(solara.Route, full=True) # type: ignore
@@ -23,43 +23,39 @@ def Fruit():
23
23
  solara.Button("Choose a fruit, I recomment banana")
24
24
  return main
25
25
 
26
- with solara.VBox() as main:
27
- with solara.HBox():
28
- for route_fruit in routes[1:]:
29
- with solara.Link(solara.resolve_path(route_fruit)):
30
- solara.Button(route_fruit.path)
26
+ with solara.Row():
27
+ for route_fruit in routes[1:]:
28
+ with solara.Link(solara.resolve_path(route_fruit)):
29
+ solara.Button(route_fruit.path)
31
30
 
32
- with solara.Link("/documentation/api/routing/use_route/fruit/nofruit", nofollow=True):
33
- solara.Button("Wrong fruit")
34
- with solara.Link("/documentation/api/routing/use_route/not-routed", nofollow=True):
35
- solara.Button("Wrong url")
36
- solara.Success(f"You chose {route.path}")
37
- return main
31
+ with solara.Link("/documentation/api/routing/use_route/fruit/nofruit", nofollow=True):
32
+ solara.Button("Wrong fruit")
33
+ with solara.Link("/documentation/api/routing/use_route/not-routed", nofollow=True):
34
+ solara.Button("Wrong url")
35
+ solara.Success(f"You chose {route.path}")
38
36
 
39
37
 
40
38
  @solara.component
41
39
  def Page():
42
40
  # this gets the top level routes, '/' and 'fruit'
43
41
  route_current, routes_all = solara.use_route()
44
- with solara.VBox() as main:
45
- with solara.Card("Navigation using buttons"):
46
- with solara.HBox():
47
- for route in routes_all:
48
- with solara.Link(route):
49
- solara.Button(route.path, color="red" if route_current == route else None)
50
- with solara.Card("Content decided by route:"):
51
- if route_current is None:
52
- solara.Error("Page does not exist")
53
- with solara.Link("fruit/kiwi"):
54
- solara.Button("Go to fruit/kiwi")
55
- elif route_current.path == "/":
56
- with solara.Link("fruit/banana"):
57
- solara.Button("Go to fruit/banana")
58
- elif route_current.path == "fruit":
59
- Fruit()
60
- else:
61
- solara.Error(f"Unknown route: {route_current.path}")
62
- return main
42
+ with solara.Card("Navigation using buttons"):
43
+ with solara.Row():
44
+ for route in routes_all:
45
+ with solara.Link(route):
46
+ solara.Button(route.path, color="red" if route_current == route else None)
47
+ with solara.Card("Content decided by route:"):
48
+ if route_current is None:
49
+ solara.Error("Page does not exist")
50
+ with solara.Link("fruit/kiwi"):
51
+ solara.Button("Go to fruit/kiwi")
52
+ elif route_current.path == "/":
53
+ with solara.Link("fruit/banana"):
54
+ solara.Button("Go to fruit/banana")
55
+ elif route_current.path == "fruit":
56
+ Fruit()
57
+ else:
58
+ solara.Error(f"Unknown route: {route_current.path}")
63
59
 
64
60
 
65
61
  routes = [
@@ -14,14 +14,12 @@ routes = [
14
14
  @solara.component
15
15
  def Page():
16
16
  route_current, routes = solara.use_route()
17
- with solara.VBox() as main:
18
- solara.Info("Note the address bar in the browser. It should change to the path of the link.")
19
- with solara.HBox():
20
- for route in routes:
21
- with solara.Link(route):
22
- current = route_current is route
23
- solara.Button(f"Go to {route.path}", color="red" if current else None)
24
- return main
17
+ solara.Info("Note the address bar in the browser. It should change to the path of the link.")
18
+ with solara.Row():
19
+ for route in routes:
20
+ with solara.Link(route):
21
+ current = route_current is route
22
+ solara.Button(f"Go to {route.path}", color="red" if current else None)
25
23
 
26
24
 
27
25
  __doc__ += apidoc(solara.Link.f) # type: ignore
@@ -6,15 +6,12 @@ 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("Nothing to see here, only in this page's source code, or by looking at the google search results for this page.")
11
- with solara.Head():
12
- solara.Meta(
13
- name="description",
14
- content="The Meta component can be used to set the description of a page. This is useful for SEO, or crawlers that index your page.",
15
- )
16
-
17
- return main
9
+ solara.Info("Nothing to see here, only in this page's source code, or by looking at the google search results for this page.")
10
+ with solara.Head():
11
+ solara.Meta(
12
+ name="description",
13
+ content="The Meta component can be used to set the description of a page. This is useful for SEO, or crawlers that index your page.",
14
+ )
18
15
 
19
16
 
20
17
  __doc__ += apidoc(solara.Meta.f) # type: ignore
@@ -26,20 +26,18 @@ def Page():
26
26
  }
27
27
  """
28
28
 
29
- with solara.VBox() as main:
30
- solara.Checkbox(label="Use CSS", value=insert_css, on_value=set_insert_css)
31
- solara.Markdown(
32
- f"""
29
+ solara.Checkbox(label="Use CSS", value=insert_css, on_value=set_insert_css)
30
+ solara.Markdown(
31
+ f"""
33
32
  ## CSS Example that styles the button below
34
33
  ```css
35
34
  {css}
36
35
  ```
37
36
  """
38
- )
39
- if insert_css:
40
- solara.Style(css)
41
- solara.Button(label="Advanced users might want to style this", icon_name="mdi-thumb-up", classes=["mybutton"])
42
- return main
37
+ )
38
+ if insert_css:
39
+ solara.Style(css)
40
+ solara.Button(label="Advanced users might want to style this", icon_name="mdi-thumb-up", classes=["mybutton"])
43
41
 
44
42
 
45
43
  __doc__ += apidoc(solara.Style.f) # type: ignore
@@ -13,20 +13,18 @@ def Page():
13
13
  path, set_path = solara.use_state(cast(Optional[Path], None))
14
14
  directory, set_directory = solara.use_state(Path("~").expanduser())
15
15
 
16
- with solara.VBox() as main:
17
- can_select = solara.ui_checkbox("Enable select")
18
-
19
- def reset_path():
20
- set_path(None)
21
- set_file(None)
22
-
23
- # reset path and file when can_select changes
24
- solara.use_memo(reset_path, [can_select])
25
- solara.FileBrowser(directory, on_directory_change=set_directory, on_path_select=set_path, on_file_open=set_file, can_select=can_select)
26
- solara.Info(f"You are in directory: {directory}")
27
- solara.Info(f"You selected path: {path}")
28
- solara.Info(f"You opened file: {file}")
29
- return main
16
+ can_select = solara.ui_checkbox("Enable select")
17
+
18
+ def reset_path():
19
+ set_path(None)
20
+ set_file(None)
21
+
22
+ # reset path and file when can_select changes
23
+ solara.use_memo(reset_path, [can_select])
24
+ solara.FileBrowser(directory, on_directory_change=set_directory, on_path_select=set_path, on_file_open=set_file, can_select=can_select)
25
+ solara.Info(f"You are in directory: {directory}")
26
+ solara.Info(f"You selected path: {path}")
27
+ solara.Info(f"You opened file: {file}")
30
28
 
31
29
 
32
30
  __doc__ += apidoc(solara.FileBrowser.f) # type: ignore
@@ -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