If you only care about using Solara from the Jupyter notebook (classic or lab) or Voila, it is:
- A set of composable components build on React-IPywidgets (e.g., Image, Markdown, FileBrowser, etcetera) to get that 'all batteries included' feeling.
- A powerful set of React hooks for cross-filtering, fetching data, downloading data, run long-running jobs in threads etc.
If you also want to develop and deploy a more extensive application and prefer not to program in a Jupyter notebook, or care about scaling your application, Solara is also:
- An application server with a good Developer eXperience (DX):
- Auto reloading refreshes the page when you save your script or Jupyter notebook.
- Your app state is restored after reload/restart: All tabs, checkboxes, etcetera will be as you set them.
- An application server that:
- Runs on
- Starlette (and thus FastAPI)
- Flask.
- Executes:
- Python scripts
- Python modules/packages
- Jupyter notebooks
- Runs on
- An alternative for Voila, for when:
- You only need to display widgets
- One kernel/process per request is becoming a bottleneck/cost issue.
- Only care about using Python (e.g., not Julia, R)
- Are ok, or even prefer, to have your application code run in the same process as your web server (e.g., Starlette/FastAPI, Flask)
If you are already using Panel, you can embed Solara components in their dashboard server.
To make it easier to use Solara, without directly having to use React-IPyWidgets, or Solara components, we can also render regular IPyWidgets
By now, you maybe understand that Solara is actually two things. A server part that takes care of getting the widgets into the browser and a UI part, consisting of react components and hooks.
The UI parts is build on top of React-IPywidgets which is using the existing IPyWidgets stack.
If you use Jupyter, then you probably use the Jupyter notebook, Lab, of Voila to get your widgets into the browser.
If you don't use Jupyter, or don't know what it is, or are a ML Ops, Dev Ops, or Sys Admin, you are probably more interested in the Solara server.
Lets start with a small example app by creating a file myapp.py
:
import react_ipywidgets as react
import react_ipywidgets.ipywidgets as w
@react.component
def Page():
clicks, set_clicks = react.use_state(0)
return w.Button(description=f"Clicked {clicks} times",
on_click=lambda: set_clicks(clicks+1))
Install and run
$ pip install solara[server]
$ solara run myapp.py
INFO: Uvicorn running on http://127.0.0.1:8765 (Press CTRL+C to quit)
INFO: Started reloader process [50178] using watchgod
INFO: Started server process [50183
The browser should open http://127.0.0.1:8765
- Create a Python script or module and assign an IPywidget instance, or React-IPywidgets component or element
Page
(or define aPage
component). - Run the server
$ solara run myapp.py
Use solara --help
for help on extra arguments.
We also support notebooks, simply assign to the Page
variable in a code cell, save your notebook, and run $ solara run myapp.ipynb
. If you widget or component is called differently, run like $ solara run myapp.ipynb:myobject.nested_widget
Solara runs on several web frameworks, such as
The development server uses Starlette, which means it is most battle-tested, but all solutions get tested in CI. Deploying using these frameworks thus is the same as deploying that framework.
The biggest difference with the development server is that all configurations should go via environment variables instead of command-line argument. For instance, if you run the development server like solara run myapp.py
, we should instead set the SOLARA_APP
environment variable to myapp.py
. For instance
$ export SOLARA_APP=myapp.py
# run flask or starlette
or look at the examples below.
For Flask, you can consult the Flask documentation.
A common solution is to use gunicorn as the WSGI HTTP Server.
A typical command would be:
$ SOLARA_APP=myapp.py gunicorn --workers 1 -b 0.0.0.0:8765 solara.server.flask:app
Note that we explicitly set --workers 1
such that it does not default to $WEB_CONCURRENCY
which can be set to higher values, such as on Heroku. See the section on Caviats why this matters.
If you already have a Flask app and want to add your Solara app behind a prefix, say '/solara/'
, you can add the Blueprint to your existing app as follows.
from flask import Flask
import solara.server.flask
app = Flask(__name__)
app.register_blueprint(solara.server.flask.blueprint, url_prefix="/solara/")
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
Save this file as "app.py"
and run
$ flask run
* Running on http://127.0.0.1:5000/
For Starlette we will assume uvicorn for the ASGI webserver, and follow their deployment documentation, except we will not use gunicorn since we will not be using multiple workers yet.
$ SOLARA_APP=myapp.py uvicorn --workers 1 -b 0.0.0.0:8765 solara.server.flask:app
Note that we explicitly set --workers 1
such that it does not default to $WEB_CONCURRENCY
which can be set to higher values, such as on Heroku. See the section on Caviats why this matters.
If you already have a Starlette app and want to add your Solara app behind a prefix, say '/solara/'
, you can mount the Solara routes to your existing app as follows.
from starlette.applications import Starlette
from starlette.routing import Mount, Route, WebSocketRoute
from starlette.responses import JSONResponse
import solara.server.starlette
def myroot():
return JSONResponse({'framework': 'solara'})
routes = [
Route("/", endpoint=myroot),
Mount("/solara/", routes=solara.server.starlette.routes)
]
app = Starlette(routes)
Since FastAPI is built on Starlette, see the section on Starlette how to deploy a Starlette app.
Use app.mount
to mount the Solara app into your existing FastAPI app.
from fastapi import FastAPI
import solara.server.fastapi
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
app.mount("/solara/", app=solara.server.fastapi.app)
If you use want to use Voila, you can use those deployment options.
⚠️ Note that you don't need to install Solara's server dependencies. E.g. 8000 installpip install solara
, notpip install solara[server]
)
Make sure you run a notebook where you display the app, e.g.
@react.component
def Page():
...
element = Page()
display(element)
Or consider using Voila-vuetify
Panel supports IPyWidgets, which means we can also embed the resulting widget from React-IPyWidgets or Solara. See their section on deployment and use the following code as an example of how to include a react component.
import panel as pn
import react_ipywidgets as react
import solara as sol
@react.component
def ButtonClick(label="Hi"):
clicks, set_clicks = react.use_state(0)
def increment():
set_clicks(clicks + 1)
return sol.Button(f"{label}: Clicked {clicks} times", on_click=increment)
# this creates just an element, Panel doesn't know what to do with that
element = ButtonClick("Solara+Panel")
# we explicitly ask React-IPyWidgets to render it, and give us the widget
button_widget, render_context = react.render_fixed(element)
# mark this panel to be served by the panel server
pn.panel(button_widget).servable()
For development/testing purposes, run this script as:
$ panel serve solara_in_panel.py
If you use Nginx as a (reverse) proxy in front of your Python web server, a minimal configuration would be:
server {
server_name widgetti.io;
listen 80
location /solara/ {
# the local solara server (could be using Starlette/uvicorn)
proxy_pass http://localhost:8765/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Script-Name /solara; # informs solara to produce correct urls
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
Note that if we use location /
instead of location /solara/
, we can skip the proxy_set_header X-Script-Name /solara
line.
An alternative to using the X-Script-Name
header with uvicorn, would be to pass the --root-path
flag, e.g.:
$ SOLARA_APP=myapp.py uvicorn --workers 1 --root-path /solara -b 0.0.0.0:8765 solara.server.flask:app
Currently, using multiple workers requires sticky sessions on the solara-context-id
cookie, so that the application context/state is in the same process the user connects to each time. Otherwise different connections may end up talking to different nodes or processes.
We plan to improve this situation in the future. In the meantime, please set your workers to 1, or go through the hassle of setting up multiple Python webservers and make your sessions sticky.
Most users:
$ pip install solara[server,examples]
Conda users (not yet):
$ conda install -c conda-forge install solara
If you want to develop on Solara, or you want to run the master branch, you can follow these steps:
First clone the repo:
$ git clone git@github.com:widgetti/solara.git
Install Solara in 'edit' mode. We use flit (pip install flit
if you don't already have it)
$ cd solara
$ flit install --pth-file --deps develop --extras server,examples
Now you can edit the source code in the git repository, without having to reinstall it.
By passing the --dev
flag, solara enters "dev" mode, which makes it friendlier for development
$ solara run myscript.py --dev
Now, if the Solara source code is edited, the server will automatically restart. Also, this enabled the --mode=development
which will:
- Load non-minified JS/CSS to make debugging easier
- Follow symlinks for the nbextensions, enabling the use of widget libraries in development mode.
The solara server automatically watches all .vue
files that are used by vue templates (there are some used in solara.components for example).
When a .vue
file is saved, the widgets get updated automatically, without needing a page reload, aiding rapid development.
If you plan to contribute, also run the following:
$ pre-commit install
This will cause a test of linters/formatters and mypy to run so the code is in good quality before you git commit.
$ playwright install
This will install playwright, for when you want to run the integration tests.
If you want to run the unit tests (quick run when doing development, or when you do test driven development)
$ py.test tests/unit
If you want to run the integration tests (uses playwright to open a browser to test the live server with a real browser)
$ py.test tests/integration
Pass the --headed
flag to see what is going on, or check out the docs
That means you simply do not use or install the server component, just use the React-ipywidgets components and run it with Voila.
No, they are different, and there are situations where I would recommend Voila instead of Solara.
If your app is compute-heavy, and a lot of the compute is happening in pure Python, the GIL (Global Interpreter Lock) can become a bottleneck with Solara, and Voila might give you better performance per client.
If you have a lot of users with short sessions, Solara spares you from having to start up a kernel for each request. Since Solara can use multiple workers in the near future, you can still scale up using multiple processes. Voila will have one process per user, while Solara can share a process with many users.
Yes, this is the preferred way of using Solara. You edit a Python script, save it, Solara will detect the file change and reload the page in your browser (no interaction is needed).
$ solara myapp.py --dev
INFO: Uvicorn running on http://127.0.0.1:8765 (Press CTRL+C to quit)
INFO: Started reloader process [50178] using watchgod
INFO: Started server process [50183
... file change detected ...
(server refreshes, your page will reload)
Over time, when your application becomes larger, you probably want to structure your application into a Python package. Instead of a filename, you pass in the package name on the command line
$ solara mystartup.killerapp --dev
....
Yes, Solara will execute each cell, and after that will look for a variable or component called Page
, like with a normal script. All other output, Markdown or other types of cells will be ignored.
Yes, take a look at the solara.server.starlette
and solara.server.fastapi
and solara.server.flask
module. The usage will change over time, so read the source and be ready to change this in the future. We do plan to provide a stable API for this in the future.
Add the line
fs.inotify.max_user_watches=524288
To your /etc/sysctl.conf file, and run sudo sysctl -p.
Or if you are using visual studio code, please read: https://code.visualstudio.com/docs/setup/linux#_visual-studio-code-is-unable-to-watch-for-file-changes-in-this-large-workspace-error-enospc
Voila and Solara set the following environment variables (based on the CGI spec):
- SERVER_SOFTWARE
- Solara: e.g. 'solara/1.2.3'
- Voila: e.g. 'voila/1.2.3'
- SERVER_PORT (e.g. '8765')
- SERVER_NAME (e.g. 'killerapp.com')
- SCRIPT_NAME (only Voila, e.g. 'voila/render/notebook.ipynb')
- PATH_TRANSLATED (only Solara e.g. '/mnt/someapp/app.py')
Jupyter Notebook/Lab/Server do not set these variables. With this information, it should be possible to recognize in which environment you are running in.