Skip to content

Commit

Permalink
docs(jupyter): add guide section to jupyter
Browse files Browse the repository at this point in the history
  • Loading branch information
jourdain committed Apr 8, 2024
1 parent 7e480b9 commit b664273
Show file tree
Hide file tree
Showing 11 changed files with 264 additions and 2 deletions.
12 changes: 11 additions & 1 deletion docs/vitepress/.vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,21 @@ export default defineConfig({
{ text: 'ParaView', link: '/guide/tutorial/paraview' },
]
},
{
text: 'Jupyter',
items: [
{ text: 'Trame in Jupyter', link: '/guide/jupyter/intro' },
{ text: 'Code example', link: '/guide/jupyter/sample-code' },
{ text: 'How it works', link: '/guide/jupyter/how-it-works' },
{ text: 'Advanced usecase', link: '/guide/jupyter/advanced' },
{ text: 'Trame extension', link: '/guide/jupyter/extension' },
]
},
{
text: 'Deployment',
items: [
{ text: 'Python CLI', link: '/guide/deployment/pypi' },
{ text: 'Jupyter', link: '/guide/deployment/jupyter' },
{ text: 'Jupyter', link: '/guide/jupyter/intro' },
{ text: 'Desktop', link: '/guide/deployment/desktop' },
{ text: 'Cloud', link: '/guide/deployment/cloud' },
{ text: 'HPC / Clusters', link: '/guide/deployment/hpc' },
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/vitepress/assets/images/deployment/tauri.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion docs/vitepress/guide/deployment/desktop.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@ pip install \
python -m trame.app.demo --app
```

![Simple trame app](/assets/images/deployment/cone-app.png)
![Simple trame app](/assets/images/deployment/cone-app.png)

![Tauri](/assets/images/deployment/tauri.svg)

Otherwise you can also use tauri and fully leverage File Dialog, Multi-Windows, and App bundler for Linux, Mac and Windows.
[Several examples are available](https://github.com/Kitware/trame-tauri/tree/master/examples) in its package repository.

![Tauri trame app](/assets/images/deployment/tauri-cone-app.png)
40 changes: 40 additions & 0 deletions docs/vitepress/guide/jupyter/advanced.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Advanced usecase

Now that you know we rely on an __`<iframe />`__ to serve the trame application, let's explore how you can tune various parameters to get the most of trame within Jupyter.

## Network / iframe builder

Jupyter provide various way to help network application to still work in their environment. You will often find a server proxy extension available, but we also created an extension for trame to better manage the network. But either way, it boils down to selecting an __iframe_builder__. In some case we try to make that selection for you automatically, but sometime you may want to override the default behavior.

Let's review the various available options

```python
from module import App

app = App()
await app.ui.ready
app.ui.iframe_builder = "..." # <= force an iframe builder
app.ui
```

| iframe_builder | URL | Default if |
| --- | --- | --- |
| default | {iframe_base_url}:{server.port}/ | Default when nothing is set |
| serverproxy | {iframe_base_url}/{server.port}/ | - |
| jupyter-extension | ENV(TRAME_JUPYTER_WWW)/servers/{server.name}/ | Extension loaded and available |
| jupyter-hub | ENV(JUPYTERHUB_SERVICE_PREFIX)proxy/{server.port}/ | If JUPYTERHUB_SERVICE_PREFIX exist |
| jupyter-hub-host | ENV(JUPYTERHUB_SERVICE_PREFIX)proxy/{host}:{server.port}/ | Never a default |

The selection can also be done via the __TRAME_IFRAME_BUILDER__ environment variable.

On top of those presets, you can directly set a function as the iframe_builder. Below is

## Configurable properties

On top of the __iframe_builder__, the following set of attributes can be set by the user.

| Property name | Default value | Layout constructor |
| --- | --- | --- |
| iframe_style | `border: none; width: 100%; height: 600px;` | `width="100%", height="600px",`
| iframe_attrs | `{}` | - |
| iframe_base_url | `http://localhost` | `base_url="http://localhost"` |
33 changes: 33 additions & 0 deletions docs/vitepress/guide/jupyter/extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# trame-jupyter-extension

The trame-jupyter-extension as opposed to server proxy will use the existing WebSocket connection to exchange data between the kernel and the client and serve the HTML content from the Jupyter web server.

For __Jupyter Lab v3__, you must use the __"trame-jupyter-extension<2"__, while Jupyter Lab v4 will work with the latest version of the extension.

You can install the extension by running `pip install trame-jupyter-extension` or via the __conda-forge__ channel.

## Debugging checklist

While the extension should just work, sometime things are not easy. This section, focus on the various steps needed to ensure that everything is a expected.

1. The extension must be installed on the Jupyter Lab Server (not the kernel).
- __Test__: In the browser, open the "Dev tools", and check if `trameJupyter` is available in the console.
- __TroubleShoot__
- Extension not available in python `=> pip install correct version`
- Extension not enabled in the Web UI
1. Make sure the extension properly configured the Kernel
- __Test__: In a Python cell the environment variable should not be empty
```python
import os
print(os.environ.get("TRAME_JUPYTER_WWW"))
```
- __TroubleShoot__
- Plugin failed to activate `=> ??`
1. Make sure the iframe_builder use the extension resolver
- __Test__: In a Python cell run the following command and inspect the URL
```python
app.ui._jupyter_content()
```
- __TroubleShoot__
- Plugin failed to activate `=> ??`
- Someone override the iframe_builder?
32 changes: 32 additions & 0 deletions docs/vitepress/guide/jupyter/how-it-works.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# How does it work?

You might be wondering what more is needed as things are pretty simple so far. Well you may relize that while everything is working fine locally, when deployed in the cloud, it does not work anymore and you don't know why. This guide aims to cover how things works under the cover and what you can tune to make it work in your environment.
Let's review the basic flow.

```python
from module import App # Get the application class

app = App() # a) Instantiate application (with default server)
await app.ui.ready # b) Wait for the UI to be ready
app.ui # c) Display UI in cell output
```

## Application instantiation

In the __(a)__ step, we instantiate the application but more importantly we lookup a trame server based on the constructor argument.
When not provided, we get the default server. So instantiating the application twice will not lead to any isolation between them as they will reflect the exact same server state.

## Waiting for the User Interface

In the __(b)__ step, we asynchronously wait for something before executing the following lines...
But technically, we are waiting for the associated server to run as a task and be ready to accept connections.
This means that instead of running the server as the main process, we start it as a background task in the current asynchronous runtime.

## Display the User Interface

In the __(c)__ step, we simply return the layout object which Jupyter will use its `_jupyter_content_` method to display it in the cell. But technically we are building an `<iframe />` pointing to our webserver (host:port) that is running as a task inside the Jupyter Kernel.

As you can imagine, when deployed in the cloud, such default behavior won't work due to missing network route. But several solutions are available and covered in the next guide.



50 changes: 50 additions & 0 deletions docs/vitepress/guide/jupyter/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Trame and Jupyter Lab

A trame application, while working in standlone fashion, can also be imported inside Jupyter and displayed within a notebook.
To make that possible, the user will need to be able to import and instantiate such application in a Jupyter context. Then, the user will need to have access to the layout (ui) of that application so it can be displayed in the notebook flow.

## Simple example

If you want to give it a try, you can setup a virtual environment like below:

```bash
# Create virtual-environment
python3 -m venv .venv

# Activate environment
source .venv/bin/activate # => Linux / Mac
# .\.venv\Scripts\activate # => Window

# Install dependencies
pip install trame trame-vtk trame-vuetify # adding vuetify + vtk.js for demo app
pip install setuptools # used in trame-vuetify but not always available nowaday
pip install jupyterlab
```

Then you can start Jupyter Lab and run the follow cells

```bash
# start Jupyter
jupyter lab
```

Then within a new notebook, you can import our trame cone demo example (We'll look into the code later).

```python
from trame.app.demo import Cone

# Create new application instance
app = Cone()

# Let's start the server by waiting for its UI to be ready
await app.ui.ready

# Put the UI into the resulting cell
app.ui
```

This should look like

![Cone in Jupyter](/assets/images/jupyter/jupyter-cone.png)

If you want [more examples using the same code, you can look at that binder example repository](https://github.com/Kitware/trame-binder).
89 changes: 89 additions & 0 deletions docs/vitepress/guide/jupyter/sample-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Sample code

In the introduction we just imported an existing example application, but let's look at its internal and explain it.

::: code-group
<<< @/../../trame/app/demo.py
:::

## Key take away

When a trame application is laid out like above, you can just run the following inside a cell to access it.

```python
from module import App

app = App()
await app.ui.ready
app.ui
```

And in case you want to have a second instance independent of the first one, you can do

```python
app2 = App('v2')
await app2.ui.ready
app2.ui
```

## Changing application state

In trame you simply can change a state variable to see the change reflected in the UI. But when doing that in another cell, it does not seems to work.
The reason is the same as when you update a state variable from an asynchronous task, you need to be explicit when the state synchronization needs to happen. A simpler way to do that is to use the state as context manager like below.

```python
with app.state:
app.state.resolution = 24
```

That is why in our setter we added such logic which allow us to simply call from anywhere

```python
app.resolution = 32
```

## Create several small UI

```python
from trame.app import get_server
from trame.widgets import html
from trame.ui.html import DivLayout

server = get_server()
state = server.state

def reset_slider():
state.slider_a = 2

@state.change("slider_a")
def udpate_result(slider_a, **_):
state.result = slider_a / 2

with DivLayout(server, 'a', height=30) as ui_a:
html.Input(
type="range",
min=-1,
max=50,
step=0.1,
v_model_number=("slider_a", 2),
style="width: 100%;",
)

await ui_a.ready
ui_a
```

Then you can create more ui on the same server

```python
with DivLayout(server, 'b', height=30) as ui_b:
html.Button(
"Reset Value",
click=reset_slider,
)
html.Span("{{ slider_a }} / 2 = {{ result }}", style="margin-left: 2rem")

ui_b
```

![Multi-UI in Jupyter](/assets/images/jupyter/multi-ui.png)

0 comments on commit b664273

Please sign in to comment.