solara-ui 1.31.0__py2.py3-none-any.whl → 1.32.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 (43) hide show
  1. solara/__init__.py +1 -1
  2. solara/components/applayout.py +6 -2
  3. solara/components/datatable.py +5 -12
  4. solara/components/markdown.py +38 -27
  5. solara/lab/hooks/dataframe.py +1 -12
  6. solara/lab/utils/dataframe.py +40 -0
  7. solara/minisettings.py +13 -5
  8. solara/server/flask.py +3 -5
  9. solara/server/kernel_context.py +110 -60
  10. solara/server/server.py +8 -4
  11. solara/server/settings.py +23 -1
  12. solara/server/starlette.py +4 -5
  13. solara/server/static/main-vuetify.js +3 -1
  14. solara/server/static/solara_bootstrap.py +1 -1
  15. solara/server/templates/solara.html.j2 +4 -4
  16. solara/tasks.py +19 -10
  17. solara/toestand.py +22 -13
  18. solara/website/assets/custom.css +13 -0
  19. solara/website/components/algolia.py +6 -0
  20. solara/website/components/algolia_api.vue +2 -1
  21. solara/website/components/header.py +9 -17
  22. solara/website/components/notebook.py +1 -1
  23. solara/website/components/sidebar.py +91 -0
  24. solara/website/pages/__init__.py +25 -67
  25. solara/website/pages/changelog/__init__.py +2 -0
  26. solara/website/pages/changelog/changelog.md +25 -0
  27. solara/website/pages/contact/__init__.py +2 -0
  28. solara/website/pages/documentation/__init__.py +2 -88
  29. solara/website/pages/documentation/advanced/content/10-howto/30-testing.md +267 -16
  30. solara/website/pages/documentation/advanced/content/15-reference/41-asset-files.md +36 -0
  31. solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +4 -4
  32. solara/website/pages/documentation/advanced/content/30-enterprise/10-oauth.md +11 -2
  33. solara/website/pages/documentation/advanced/content/40-development/10-setup.md +1 -1
  34. solara/website/pages/documentation/faq/content/99-faq.md +27 -0
  35. solara/website/pages/documentation/getting_started/content/02-installing.md +2 -2
  36. solara/website/pages/documentation/getting_started/content/04-tutorials/60-jupyter-dashboard-part1.py +50 -49
  37. solara/website/pages/documentation/getting_started/content/07-deploying/10-self-hosted.md +20 -4
  38. {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/METADATA +2 -2
  39. {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/RECORD +43 -41
  40. {solara_ui-1.31.0.data → solara_ui-1.32.1.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
  41. {solara_ui-1.31.0.data → solara_ui-1.32.1.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
  42. {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/WHEEL +0 -0
  43. {solara_ui-1.31.0.dist-info → solara_ui-1.32.1.dist-info}/licenses/LICENSE +0 -0
@@ -5,20 +5,165 @@ description: Using solara you can test both the front and back end functionaliti
5
5
 
6
6
  # Testing with Solara
7
7
 
8
+ When possible, we recommend to test your application without a browser. This is faster and more reliable than testing with a browser. Testing via a browser is more difficult to get right due to having to deal with two processes that communicate
9
+ asynchronously (the Python process and the browser process).
10
+
11
+ Only when you develop new components that rely on new frontend code or CSS do we recommend considering using a browser to test your component or application.
8
12
 
9
13
  ## Testing without a Browser
10
14
 
11
- We recommend using pytest to test the application logic of your Solara components. To get inspiration for writing tests that cover component logic and their interactions with existing components, refer to the [tests in the Solara repository](https://github.com/widgetti/solara/tree/master/tests).
15
+ When testing a component or application without a browser, we recommend to use vanilla [pytest](https://docs.pytest.org/) to test the application logic.
16
+
17
+ To get inspiration for writing tests that cover component logic and their interactions with existing components, refer to the [tests in the Solara repository](https://github.com/widgetti/solara/tree/master/tests).
18
+
19
+ The following example demonstrates how to test a simple Solara component using pytest:
20
+
21
+ ```python
22
+ import solara
23
+ import ipyvuetify as v
24
+
25
+
26
+ def test_docs_no_browser_simple():
27
+ clicks = solara.reactive(0)
28
+
29
+ @solara.component
30
+ def ClickButton():
31
+ def increment():
32
+ clicks.value += 1
33
+
34
+ solara.Button(label=f"Clicked: {clicks}", on_click=increment)
35
+
36
+ # rc is short for render context
37
+ box, rc = solara.render(ClickButton(), handle_error=False)
38
+ button = box.children[0]
39
+ assert isinstance(button, v.Btn)
40
+ assert button.children[0] == "Clicked: 0"
41
+ # trigger the click event handler without a browser
42
+ button.click()
43
+ assert clicks.value == 1
44
+ assert button.children[0] == "Clicked: 1"
45
+ ```
46
+
47
+ Here we let Solara render the component into a set of widgets without a frontend (browser) connected.
48
+ We check the resulting ipywidgets and its properties using `asserts`, as is standard with pytest.
49
+ We also show how to trigger the click handler from the Python side using [`ipyvue`'s](https://github.com/widgetti/ipyvue) `.click()` method
50
+ on the widget, again without requiring a browser.
51
+
52
+ Run this test with pytest as follows:
53
+
54
+ ```bash
55
+ pytest tests/unit/test_docs_no_browser_simple.py
56
+ ```
57
+
58
+
59
+ ### Finding a widget in the widget tree
60
+
61
+ When widgets are embedded in a larger widget tree, it becomes cumbersome to find the widget you are looking for using `.children[0].children[1]...` etc. For this use case we can use the `rc.find` method to look for a particular widget. This API is inspired on the playwright API, and is a convenient way to find a widget in the widget tree.
62
+
63
+ ```python
64
+ import solara
65
+ import ipyvuetify as v
66
+
67
+
68
+ def test_docs_no_browser_api_find():
69
+ clicks = solara.reactive(0)
70
+
71
+ @solara.component
72
+ def ClickButton():
73
+ def increment():
74
+ clicks.value += 1
75
+
76
+ with solara.Card("Button in a card"):
77
+ with solara.Column().meta(ref="my_column"):
78
+ solara.Button(label=f"Clicked: {clicks}", on_click=increment)
79
+ with solara.Column():
80
+ solara.Button(label="Not the button we need")
81
+
82
+ # rc is short for render context
83
+ box, rc = solara.render(ClickButton(), handle_error=False)
84
+ # this find will make the .widget fail, because it matches two buttons
85
+ # finder = rc.find(v.Btn)
86
+ # We can refine our search by adding constraints to attributes of the widget
87
+ button_locator = rc.find(v.Btn, children=["Clicked: 0"])
88
+ # basics asserts are supported, like assert_single(), assert_empty(), assert_not_empty()
89
+ button_locator.assert_single()
90
+ button = button_locator.widget
91
+ # .find calls can also be nested, and can use the meta_ref to find the right widget
92
+ # finder = rc.find(meta_ref="my_column").find(v.Btn)
93
+ button.click()
94
+ assert clicks.value == 1
95
+ rc.find(v.Btn, children=["Clicked: 1"]).assert_single()
96
+ ```
97
+
98
+ By including keywords arguments to the `find` method, we can get more specific about the widget we are looking for.
99
+ In the above example, a simple `.find(v.Btn)` would find two buttons, while `.find(v.Btn, children=["Clicked: 0"])` will find the button we are looking for. *(Note that this does require knowing about the internal implementation
100
+ of the Button component: i.e. `solara.Button` creates a `v.Btn`, and the label argument causes the button having `children=["Clicked 0"]`)*.
101
+
102
+
103
+ Because sometimes it is difficult to find a specific widget, we made is possible to attach meta data to a widget and
104
+ use that to find widgets. Together with nesting (i.e. `.find(...).find(...)`) calls, this makes it easier to find the widget you are looking for in
105
+ larger applications. In the above example we could have replaced the `.find(v.Btn, children=["Clicked: 0"])` with
106
+ `.find(meta_ref="my_column").find(v.Btn)` to find the button we are looking for.
107
+
108
+ Especially in larger application, adding meta data to widgets makes it much easier to find the widget you are looking for, as well
109
+ as correlate the testing code back to the application code. Having unique meta_refs makes searching through your codebase and in your tests much easier.
110
+
111
+ ### Asynchronous updating of the UI
112
+
113
+ When a [`solara.lab.task`](https://solara.dev/api/task) is executed, a new thread will spawn, which will likely update the UI somewhere in the future. We can wait for the UI to update using the `wait_for` method on the finder object. This method will poll the widget tree, waiting for the widget to appear. If the timeout is reached, the test will fail.
114
+
115
+ ```python
116
+ import solara
117
+ import solara.lab
118
+ import ipyvuetify as v
119
+ import time
120
+
121
+
122
+ def test_docs_no_browser_api_thread():
123
+ clicks = solara.reactive(0)
124
+
125
+ @solara.component
126
+ def ClickButton():
127
+ @solara.lab.task
128
+ def increment():
129
+ # now we will wait for 0.3 seconds before updating the UI
130
+ time.sleep(0.3)
131
+ clicks.value += 1
132
+
133
+ with solara.Card("Button in a card"):
134
+ with solara.Column():
135
+ solara.Button(label=f"Clicked: {clicks}", on_click=increment)
136
+
137
+ # rc is short for render context
138
+ box, rc = solara.render(ClickButton(), handle_error=False)
139
+ finder = rc.find(v.Btn)
140
+ button = finder.widget
141
+ finder.assert_single()
142
+ finder.assert_not_empty()
143
+ assert button.children[0] == "Clicked: 0"
144
+
145
+ # clicking will now start a thread, so we have to wait/poll for the UI to update
146
+ button.click()
147
+
148
+ button_after_delayed_click = rc.find(v.Btn, children=["Clicked: 1"])
149
+ button_after_delayed_click.wait_for(timeout=2.5)
150
+ ```
12
151
 
13
152
  ## Testing with a Browser
14
153
 
154
+ As mentioned in the introduction, when you develop new components that need frontend code or CSS, we recommend considering using a browser to test your component or application. Although these tests are slower to run and more
155
+ difficult to get right, they may be crucial to ensure the correct rendering of your components or application.
156
+
157
+
15
158
  ### Installation
16
159
 
17
- Solara is using the `pytest-ipywidgets` pytest plugin together with [Playwright for Python](https://playwright.dev/python/) to test your widgets, components or applications using a browser, for both unit as well as integration tests.
160
+ We recommend using the `pytest-ipywidgets` pytest plugin together with [Playwright for Python](https://playwright.dev/python/) to test your widgets, components or applications using a browser, for both unit as well as integration tests.
161
+
162
+ Unit tests often test a single component, while integration (or smoke tests) usually tests your whole application, or a large part of it.
18
163
 
19
164
  To install `pytest-ipywidgets` and Playwright for Python, run the following commands:
20
165
  ```
21
- $ pip install "pytest-ipywidgets[solara]" # or "pytest-ipywidgets[all]" if you also want to test with Jupyter Lab, Jupiter Notebook and Voila.
166
+ $ pip install "pytest-ipywidgets[solara]" # or "pytest-ipywidgets[all]" if you also want to test with Jupyter Lab, Jupyter Notebook and Voila.
22
167
  $ playwright install chromium
23
168
  ```
24
169
 
@@ -27,14 +172,13 @@ $ playwright install chromium
27
172
  The most convenient way to test a widget, is by including the `solara_test` fixture in your test function arguments. Here's an example:
28
173
 
29
174
  ```python
30
- # file tests/ui/test_widget_button.py
31
175
  import ipywidgets as widgets
32
176
  import playwright.sync_api
33
177
  from IPython.display import display
34
178
 
35
179
  def test_widget_button_solara(solara_test, page_session: playwright.sync_api.Page):
36
- # this all runs in process, which only works with solara
37
- # also, this test is only with pure ipywidgets
180
+ # The test code runs in the same process as solara-server (which runs in a separate thread)
181
+ # Note: this test uses ipywidgets directly, not solara components.
38
182
  button = widgets.Button(description="Click Me!")
39
183
 
40
184
  def change_description(obj):
@@ -57,10 +201,114 @@ pytest tests/ui/test_widget_button.py --headed # remove --headed to run headless
57
201
  ```
58
202
 
59
203
 
204
+ ### Testing state changes on the Python side with polling
205
+
206
+ In the above example, an event in the frontend led to a state change on the Python side which is reflected in
207
+ the frontend, so we could use Playwright to test if our event handler was executed correctly.
60
208
 
61
- # Testing in the main Jupyter Environments
209
+ However, sometimes we want to test if a state changed on the Python side that has no
210
+ direct effect on the frontend. A possible example is is a successful database write, or an update to a
211
+ Python variable.
62
212
 
63
- In case you want to test your component in the main Jupyter environments (e.g., Jupyter Notebook, Jupyter Lab, Voila, and Solara) to ensure it renders correctly, use the `ipywidgets_runner` fixture to run code snippets. Here's an example:
213
+ The following example uses a polling technique to check if a state change happened on the Python side.
214
+
215
+ ```python
216
+ import ipywidgets as widgets
217
+ import playwright.sync_api
218
+ from IPython.display import display
219
+ from typing import Callable
220
+ import time
221
+
222
+
223
+ def assert_equals_poll(getter: Callable, expected, timeout=2, iteration_delay=0.01):
224
+ start = time.time()
225
+ while time.time() - start < timeout:
226
+ if getter() == expected:
227
+ return
228
+ time.sleep(iteration_delay)
229
+ assert getter() == expected
230
+ return False
231
+
232
+
233
+ def test_event_with_polling(solara_test, page_session: playwright.sync_api.Page):
234
+ button = widgets.Button(description="Append data")
235
+ # some data that will change due to a button click
236
+ click_data = []
237
+
238
+ def on_click(button):
239
+ # change the data when the button is clicked
240
+ # this will be called from the thread the websocket is in
241
+ # so we can block/poll from the main thread (that pytest is running in)
242
+ click_data.append(42)
243
+
244
+ button.on_click(on_click)
245
+ display(button)
246
+ button_sel = page_session.locator("text=Append data")
247
+ button_sel.click()
248
+
249
+ # we block/poll until the condition is met.
250
+ assert_equals_poll(lambda: click_data, [42])
251
+ ```
252
+
253
+ ### Testing state changes on the Python side with a Future
254
+
255
+ Sometimes, state changes on the Python side emit an event that we can capture. In this case,
256
+ we can use a `concurrent.futures.Future` to block until the state change happens. This is a more
257
+ efficient way to wait for a state change than polling.
258
+
259
+ ```python
260
+ import ipywidgets as widgets
261
+ from concurrent.futures import Future
262
+ import playwright.sync_api
263
+ from IPython.display import display
264
+
265
+
266
+ def future_trait_change(widget, attribute):
267
+ """Returns a future that will be set when the trait changes."""
268
+ future = Future() # type: ignore
269
+
270
+ def on_change(change):
271
+ # set_result will cause the .result() call below to resume
272
+ future.set_result(change["new"])
273
+ widget.unobserve(on_change, attribute)
274
+
275
+ widget.observe(on_change, attribute)
276
+ return future
277
+
278
+
279
+ def test_event_with_polling(solara_test, page_session: playwright.sync_api.Page):
280
+ button = widgets.Button(description="Reset slider")
281
+ slider = widgets.IntSlider(value=42)
282
+
283
+ def on_click(button):
284
+ # change the slider value trait when the button is clicked
285
+ # this will be called from the thread the websocket from solara-server
286
+ # is running in, so we can block from the main thread (that pytest is running in)
287
+ slider.value = 0
288
+
289
+ button.on_click(on_click)
290
+ display(button)
291
+ # we could display the slider, but it's not necessary for this test
292
+ # since we are only testing if the value changes on the Python side
293
+ # display(slider)
294
+ button_sel = page_session.locator("text=Reset slider")
295
+
296
+ # create the future with the attached observer *before* clicking the button
297
+ slider_value = future_trait_change(slider, "value")
298
+ # trigger the click event handler via the frontend, this makes sure that
299
+ # the event handler (on_click) gets executed in a separate thread
300
+ # (the one that the websocket from solara-server is running in)
301
+ button_sel.click()
302
+
303
+ # .result() blocks until the value changes or the timeout condition is met.
304
+ # If no value is set, the test will fail due to a TimeoutError
305
+ assert slider_value.result(timeout=2) == 0
306
+ ```
307
+
308
+
309
+ ### Testing in Voila, Jupyter Lab, Jupyter Notebook, and Solara
310
+
311
+ In case you want to test your component in the multiple Jupyter environments (e.g., Jupyter Notebook, Jupyter Lab, Voila, and Solara) to ensure it renders correctly, use the `ipywidgets_runner` fixture to run code snippets. Here's an example:
64
312
 
65
313
  ```python
66
314
  import ipywidgets as widgets
@@ -98,7 +346,10 @@ Note that the function in the code will be executed in a different process (a Ju
98
346
  Because the function code executes in the kernel, you do not have access to local variables. However, by passing a dictionary as second argument
99
347
  to `ipywidgets_runner` we can pass in extra local variables (e.g. `ipywidgets_runner(kernel_code, {"extra_argument": extra_argument})`).
100
348
 
101
- ## Limiting the Jupyter Environments
349
+ These tests run slow, and are generally only recommended for ipywidgets authors that want to test if their library works in all Jupyter environments. We use these kinds of tests in libraries such as [ipyvue](https://github.com/widgetti/ipyvue), [ipyvuetify](https://github.com/widgetti/ipyvuetify), [ipyaggrid](https://github.com/widgetti/ipyaggrid), but should in general not be needed for most applications.
350
+
351
+
352
+ ### Limiting the Jupyter Environments
102
353
  To limit the ipywidgets_runner fixture to only run in a specific environment, use the `SOLARA_TEST_RUNNERS` environment variable:
103
354
 
104
355
  * `SOLARA_TEST_RUNNERS=solara pytest tests/ui`
@@ -109,7 +360,7 @@ To limit the ipywidgets_runner fixture to only run in a specific environment, us
109
360
 
110
361
 
111
362
 
112
- # Organizing Tests and Managing Snapshots
363
+ ### Organizing Tests and Managing Snapshots
113
364
  We recommend organizing your visual tests in a separate directory, such as `tests/ui`. This allows you to run fast tests (`test/unit`) separately from slow tests (t`est/ui`). Use the `solara_snapshots_directory` fixture to change the default directory for storing snapshots, which is `tests/ui/snapshots` by default.
114
365
 
115
366
  ```bash
@@ -123,7 +374,7 @@ To compare a captured image from a part of your page with the reference image, u
123
374
  For local development, you can use the --solara-update-snapshots flag to update the reference images. This will overwrite the existing reference images with the new ones generated during the test run. However, you should carefully review the changes before committing them to your repository to ensure the updates are accurate and expected.
124
375
 
125
376
 
126
- # Continuous Integration Recommendations
377
+ ### Continuous Integration Recommendations
127
378
 
128
379
  When a test fails, the output will be placed in a directory structure similar to what would be put in the `solara_snapshots_directory` directory but under the test-results directory in the root of your project (unless changed by passing `--output=someotherdirectory` to pytest).
129
380
 
@@ -141,22 +392,22 @@ In CI, we recommend downloading this directory using, for example, GitHub Action
141
392
  After inspecting and approving the screenshots, you can copy them to the `solara_snapshots_directory` directory and commit them to your repository. This way, you ensure that the reference images are up-to-date and accurate for future tests.
142
393
 
143
394
 
144
- # Note about the Playwright
395
+ ### Note about the Playwright
145
396
 
146
397
  Visual testing with solara is based on [Playwright for Python](https://playwright.dev/python/), which provides a `page` fixture. However, this fixture will make a new page for each test, which is not what we want. Therefore, we provide a `page_session` fixture that will reuse the same page for all tests. This is important because it will make the tests faster.
147
398
 
148
399
  By following these recommendations and guidelines, you can efficiently test your Solara applications and ensure a smooth developer experience.
149
400
 
150
- # Configuration
401
+ ### Configuration
151
402
 
152
- ## Changing the Hostname
403
+ #### Changing the Hostname
153
404
 
154
405
  To configure the hostname the socket is bound to when starting the test server, use the `HOST` or `SOLARA_HOST` environment variable (e.g. `SOLARA_HOST=0.0.0.0`). This hostname is also used for the jupyter server and voila. Alternatively the `--solara-host` argument can be passed on the command line for pytest.
155
406
 
156
- ## Changing the Port
407
+ #### Changing the Port
157
408
 
158
409
  To configure the ports the socket is bound to when starting the test servers, use the `PORT` environment variable (e.g. `PORT=18865`). This port and subsequent port will be used for solara-server, jupyter-server and voila. Alternatively the `--solara-port` argument can be passed on the command line for pytest for the solara server, and `--jupyter-port` and `--voila-port` for the ports of jupyter server and voila respectively.
159
410
 
160
- ## Vuetify warmup
411
+ #### Vuetify warmup
161
412
 
162
413
  By default, we insert an ipyvuetify widget with an icon into the frontend to force loading all the vuetify assets, such as CSS and fonts. However, if you are using the solara test plugin to test pure ipywidgets or a 3rd ipywidget based party library you might not need this. Disable this vuetify warmup phase by passing the `--no-solara-vuetify-warmup` argument to pytest, or setting the environment variable `SOLARA_TEST_VUETIFY_WARMUP` to a falsey value (e.g. `SOLARA_TEST_VUETIFY_WARMUP=0`).
@@ -34,3 +34,39 @@ Putting the `assets` directory 1 level higher than the `pages` directory avoids
34
34
 
35
35
 
36
36
  Although the `assets` directory can be used for serving arbitrary files, we recommend using the [static files](/documentation/advanced/reference/static-files) directory instead, to avoid name collisions.
37
+
38
+
39
+ ## Extra asset locations
40
+
41
+ If for instance you are creating a library on top of Solara, you might want to have your own assets files, like stylesheets or JavaScript files.
42
+ For this purpose, solara-server can be configured to look into other directories for assets files by setting the `SOLARA_ASSETS_EXTRA_LOCATIONS` environment variable.
43
+ This string contains a comma-separated list of directories or Python package names to look for asset files. The directories are searched in order, after looking in the application specific directory, and the first file found is used.
44
+
45
+ For example, if we run solara as:
46
+
47
+ ```
48
+ $ export SOLARA_ASSETS_EXTRA_LOCATIONS=/path/to/assets,my_package.assets
49
+ $ solara run solara.website.pages
50
+ Solara server is starting at http://localhost:8765...
51
+ ```
52
+
53
+ And we would fetch `http://localhost:8765/static/assets/my-image.jpg`, Solara-server would look for the file in the following order:
54
+
55
+ 1. `.../solara/website/assets/my-image.jpg`
56
+ 1. `/path/to/assets/my-image.jpg`
57
+ 1. `.../my_package/assets/my-image.jpg`
58
+ 1. `...site-package/solara/server/assets/my-image.jpg`
59
+
60
+ ## Recommended pattern for libraries to add asset locations
61
+
62
+ If you are creating a library on top of Solara, and you want to programmatically add asset locations, you can do so by adding the following code to your library:
63
+
64
+ ```python
65
+ import solara.server.settings
66
+ import my_package.assets
67
+
68
+
69
+ path = my_package.assets.__path__[0]
70
+ # append at the end, so SOLARA_ASSETS_EXTRA_LOCATIONS can override
71
+ solara.server.settings.assets.extra_locations.append(path)
72
+ ```
@@ -68,7 +68,7 @@ If you do define a `Page` component, you are fully responsible for how routing i
68
68
  An example route definition could be something like this:
69
69
 
70
70
  ```python
71
- import solara as sol
71
+ import solara
72
72
 
73
73
  routes = [
74
74
  # route level == 0
@@ -103,7 +103,7 @@ routes = [
103
103
  @solara.component
104
104
  def Page():
105
105
  level = solara.use_route_level() # returns 0
106
- route_current, routes_current_level = solara.use_routes()
106
+ route_current, routes_current_level = solara.use_route()
107
107
  # route_current is routes[1], i.e. solara.Route(path="docs", children=[...])
108
108
  # routes_current_level is [routes[0], routes[1], routes[2], routes[3]], i.e.:
109
109
  # [solara.Route(path="/"), solara.Route(path="docs", children=[...]),
@@ -125,7 +125,7 @@ Now the `MyFirstLevelChildComponent` component is responsible for rendering the
125
125
  @solara.component
126
126
  def MyFirstLevelChildComponent():
127
127
  level = solara.use_route_level() # returns 1
128
- route_current, routes_current_level = solara.use_routes()
128
+ route_current, routes_current_level = solara.use_route()
129
129
  # route_current is routes[1].children[0], i.e. solara.Route(path="basics", children=[...])
130
130
  # routes_current_level is [routes[1].children[0], routes[1].children[1]], i.e.:
131
131
  # [solara.Route(path="basics", children=[...]), solara.Route(path="advanced")]
@@ -143,7 +143,7 @@ And the `MySecondLevelChildComponent` component is responsible for rendering the
143
143
  @solara.component
144
144
  def MySecondLevelChildComponent():
145
145
  level = solara.use_route_level() # returns 2
146
- route_current, routes_current_level = solara.use_routes()
146
+ route_current, routes_current_level = solara.use_route()
147
147
  # route_current is routes[1].children[0].children[0], i.e. solara.Route(path="react")
148
148
  # routes_current_level is [routes[1].children[0].children[0], routes[1].children[0].children[1], routes[1].children[0].children[2]], i.e.
149
149
  # [solara.Route(path="react"), solara.Route(path="ipywidgets"), solara.Route(path="solara")]
@@ -133,8 +133,8 @@ You can also configure Solara to use our Fief test account. To do this, you need
133
133
 
134
134
  ```bash
135
135
  SOLARA_SESSION_SECRET_KEY="change me" # required if you don't use the default test account
136
- SOLARA_OAUTH_CLIENT_ID="x2np62qgwp6hnEGTP4JYUE3igdZWhT-AvjpjwwDyKXU" # found in the Auth0 dashboard Clients->General Tab->Secret
137
- SOLARA_OAUTH_CLIENT_SECRET="XQlByE1pVIz5h2SBN2GYDwT_ziqArHJgLD3KqMlCHjg" # found in the Auth0 dashboard Clients->General Tab->ID
136
+ SOLARA_OAUTH_CLIENT_ID="x2np62qgwp6hnEGTP4JYUE3igdZWhT-AvjpjwwDyKXU" # found in the Auth0 dashboard Clients->General Tab->ID
137
+ SOLARA_OAUTH_CLIENT_SECRET="XQlByE1pVIz5h2SBN2GYDwT_ziqArHJgLD3KqMlCHjg" # found in the Auth0 dashboard Clients->General Tab->Secret
138
138
  SOLARA_OAUTH_API_BASE_URL="solara-dev.fief.dev" # found in the Fief dashboard Tenants->Base URL
139
139
  # different from Solara's default
140
140
  SOLARA_OAUTH_LOGOUT_PATH="logout"
@@ -169,3 +169,12 @@ Please note that Python 3.6 is not supported for Solara OAuth.
169
169
  ### Wrong redirection
170
170
 
171
171
  If the redirection back to solara return to the wrong address, it might be due to solara not choosing the right default for `SOLARA_BASE_URL`. For instance this variable could be set to `SOLARA_BASE_URL=https://solara.dev` for the solara.dev server. If you application runs behind a subpath, e.g. `/myapp`, you might have to set `SOLARA_ROOT_PATH=/myapp`.
172
+
173
+
174
+ ### Wrong schema detected for redirect URL
175
+
176
+ Solara needs to give the OAuth providers a redirect URL to get back to your Solara application after navigating to the OAuth provider website. For our documentation server, we ask the OAuth provider to redirect to `https://solara.dev/_solara/auth/authorize`. The protocol part (`https`) and the domain name part (`solara.dev`) or this URL is constructed from the request URL (what the browser sends to the server).
177
+
178
+ If you are running Aolara behind a reverse proxy server (like nginx), make sure that the `X-Forwarded-Proto` and `Host` headers are forwarded correctly so Solara can construct the correct redirect URL to send to the OAuth provider.
179
+
180
+ See our [self hosted deployment](https://solara.dev/documentation/getting_started/deploying/self-hosted) for more information on how to configure your reverse proxy server.
@@ -11,7 +11,7 @@ Assuming you have created a virtual environment as described in [the installatio
11
11
 
12
12
  $ git clone git@github.com:widgetti/solara.git
13
13
  $ cd solara
14
- $ pip install ".[dev,documentation]" # documentation is optional
14
+ $ pip install -r requirements-dev.txt
15
15
 
16
16
 
17
17
  ## Running Solara server in auto restart mode
@@ -74,3 +74,30 @@ Voila and Solara set the following environment variables (based on the CGI spec)
74
74
 
75
75
  Jupyter Notebook/Lab/Server do not set these variables. With this information,
76
76
  it should be possible to recognize in which environment you are running in.
77
+
78
+
79
+ ## I cannot find or run `solara` from the command line
80
+
81
+
82
+ On Linux or OSX you might see
83
+
84
+ ```
85
+ $ solara
86
+ command not found: solara
87
+ ```
88
+
89
+ On Windows you might see
90
+
91
+ ```
92
+ C:Users\myusername> solara
93
+ solara: The term 'solara' is not recognized as the name of a cdlet, function,
94
+ script file, or operable program.
95
+ ```
96
+
97
+ The solara command before version 1.30 was installed with the package `solara`, but now it is installed with the package `solara-server`.
98
+
99
+ If you upgrade from an older version to 1.30 or later, the order in which pip installs packages can cause the `solara` command to be uninstalled. To fix this, reinstall the `solara-server` package:
100
+
101
+ ```bash
102
+ $ pip install solara-server --force-reinstall
103
+ ```
@@ -89,7 +89,7 @@ $ pip install solara-air-gapped/*.whl
89
89
  The `solara` package is a meta package that installs all the necessary dependencies to get started with Solara. By default, we install:
90
90
 
91
91
  * [`pip install "solara-ui[all]"`](https://pypi.org/project/solara-ui)
92
- * [`pip install "solara-server[starlette,dev]"`](https://pypi.org/project/solara-ui) -
92
+ * [`pip install "solara-server[starlette,dev]"`](https://pypi.org/project/solara-ui)
93
93
 
94
94
  Note that the solara (meta) package will pin exact versions of solara-ui and solara-server, which ensures you always get compatible version of the subpackages.
95
95
  For more flexibility, and control over what you install, you can install the subpackages directly.
@@ -125,7 +125,7 @@ The `solara-server` packages supports the following optional dependencies:
125
125
  ### The `pytest-ipywidgets` package
126
126
 
127
127
  This package is a plugin for pytest that lets you test ipywidgets with playwright. It is useful for testing your ipywidgets or solara applications in a (headless) browser.
128
- See [Our testing documentation](https://solara.dev/docs/advanced/testing) for more information.
128
+ See [Our testing documentation](https://solara.dev/documentation/advanced/howto/testing) for more information.
129
129
 
130
130
  * `pip install "pytest-ipywidgets"` - Minimal installation for testing ipywidgets.
131
131
  * `pip install "pytest-ipywidgets[voila]"` - The above, with a compatible version of voila.
@@ -10,55 +10,56 @@ title = "Jupyter Dashboard (1/3)"
10
10
 
11
11
  @solara.component
12
12
  def Page():
13
- title = "Build your Jupyter dashboard using Solara"
14
- solara.Meta(property="og:title", content=title)
15
- solara.Meta(name="twitter:title", content=title)
16
- solara.Title(title)
13
+ with solara.Column(gap=0):
14
+ title = "Build your Jupyter dashboard using Solara"
15
+ solara.Meta(property="og:title", content=title)
16
+ solara.Meta(name="twitter:title", content=title)
17
+ solara.Title(title)
17
18
 
18
- img = "https://dxhl76zpt6fap.cloudfront.net/public/docs/tutorial/jupyter-dashboard1.webp"
19
- solara.Meta(name="twitter:image", content=img)
20
- solara.Meta(property="og:image", content=img)
19
+ img = "https://dxhl76zpt6fap.cloudfront.net/public/docs/tutorial/jupyter-dashboard1.webp"
20
+ solara.Meta(name="twitter:image", content=img)
21
+ solara.Meta(property="og:image", content=img)
21
22
 
22
- description = "Learn how to build a Jupyter dashboard and deploy it as a web app using Solara."
23
- solara.Meta(name="description", property="og:description", content=description)
24
- solara.Meta(name="twitter:description", content=description)
25
- tags = [
26
- "jupyter",
27
- "jupyter dashboard",
28
- "dashboard",
29
- "web app",
30
- "deploy",
31
- "solara",
32
- ]
33
- solara.Meta(name="keywords", content=", ".join(tags))
23
+ description = "Learn how to build a Jupyter dashboard and deploy it as a web app using Solara."
24
+ solara.Meta(name="description", property="og:description", content=description)
25
+ solara.Meta(name="twitter:description", content=description)
26
+ tags = [
27
+ "jupyter",
28
+ "jupyter dashboard",
29
+ "dashboard",
30
+ "web app",
31
+ "deploy",
32
+ "solara",
33
+ ]
34
+ solara.Meta(name="keywords", content=", ".join(tags))
35
+ with solara.Column():
36
+ Notebook(
37
+ Path(HERE / "_jupyter_dashboard_1.ipynb"),
38
+ show_last_expressions=True,
39
+ execute=False,
40
+ outputs={
41
+ "a7d17a84": None, # empty output (7)
42
+ # original: https://github.com/widgetti/solara/assets/1765949/e844acdb-c77d-4df4-ba4c-a629f92f18a3
43
+ "82f1d2f7": solara.Image("https://dxhl76zpt6fap.cloudfront.net/pages/docs/content/60-jupyter-dashboard-part1/map.webp"), # map (11)
44
+ "3e7ea361": None, # (13)
45
+ # original: https://github.com/widgetti/solara/assets/1765949/daaa3a46-61f5-431f-8003-b42b5915da4b
46
+ "56055643": solara.Image("https://dxhl76zpt6fap.cloudfront.net/pages/docs/content/60-jupyter-dashboard-part1/view.webp"), # View (15)
47
+ # original: https://github.com/widgetti/solara/assets/1765949/2f4daf0f-b7d8-4f70-b04a-c27542cffdb0
48
+ "c78010ec": solara.Image("https://dxhl76zpt6fap.cloudfront.net/pages/docs/content/60-jupyter-dashboard-part1/page.webp"), # Page (20)
49
+ # original: https://github.com/widgetti/solara/assets/1765949/a691d9f1-f07b-4e06-b21b-20980476ad64
50
+ "18290364": solara.Image("https://dxhl76zpt6fap.cloudfront.net/pages/docs/content/60-jupyter-dashboard-part1/controls.webp"), # Controls
51
+ "0ca68fe8": None,
52
+ "fef5d187": None,
53
+ # original: https://github.com/widgetti/solara/assets/1765949/f0075ad1-808d-458c-8797-e460ce4dc06d
54
+ "af686391": solara.Image("https://dxhl76zpt6fap.cloudfront.net/pages/docs/content/60-jupyter-dashboard-part1/full-app.webp"), # Full app
55
+ },
56
+ )
57
+ solara.Markdown(
58
+ """
59
+ Explore this app live at [solara.dev](/apps/jupyter-dashboard-1).
34
60
 
35
- Notebook(
36
- Path(HERE / "_jupyter_dashboard_1.ipynb"),
37
- show_last_expressions=True,
38
- execute=False,
39
- outputs={
40
- "a7d17a84": None, # empty output (7)
41
- # original: https://github.com/widgetti/solara/assets/1765949/e844acdb-c77d-4df4-ba4c-a629f92f18a3
42
- "82f1d2f7": solara.Image("https://dxhl76zpt6fap.cloudfront.net/pages/docs/content/60-jupyter-dashboard-part1/map.webp"), # map (11)
43
- "3e7ea361": None, # (13)
44
- # original: https://github.com/widgetti/solara/assets/1765949/daaa3a46-61f5-431f-8003-b42b5915da4b
45
- "56055643": solara.Image("https://dxhl76zpt6fap.cloudfront.net/pages/docs/content/60-jupyter-dashboard-part1/view.webp"), # View (15)
46
- # original: https://github.com/widgetti/solara/assets/1765949/2f4daf0f-b7d8-4f70-b04a-c27542cffdb0
47
- "c78010ec": solara.Image("https://dxhl76zpt6fap.cloudfront.net/pages/docs/content/60-jupyter-dashboard-part1/page.webp"), # Page (20)
48
- # original: https://github.com/widgetti/solara/assets/1765949/a691d9f1-f07b-4e06-b21b-20980476ad64
49
- "18290364": solara.Image("https://dxhl76zpt6fap.cloudfront.net/pages/docs/content/60-jupyter-dashboard-part1/controls.webp"), # Controls
50
- "0ca68fe8": None,
51
- "fef5d187": None,
52
- # original: https://github.com/widgetti/solara/assets/1765949/f0075ad1-808d-458c-8797-e460ce4dc06d
53
- "af686391": solara.Image("https://dxhl76zpt6fap.cloudfront.net/pages/docs/content/60-jupyter-dashboard-part1/full-app.webp"), # Full app
54
- },
55
- )
56
- solara.Markdown(
57
- """
58
- Explore this app live at [solara.dev](/apps/jupyter-dashboard-1).
59
-
60
- Don’t miss the next tutorial and stay updated with the latest techniques and insights by subscribing to our newsletter.
61
- """
62
- )
63
- location = solara.use_router().path
64
- MailChimp(location=location)
61
+ Don’t miss the next tutorial and stay updated with the latest techniques and insights by subscribing to our newsletter.
62
+ """
63
+ )
64
+ location = solara.use_router().path
65
+ MailChimp(location=location)