Streamsync is a new open-source framework that creates data apps. Streamlit allows you to build the user interface using a visual editor while retaining the power of Python in the backend. It’s all contained in a pip-installable package
The Origins
About a year ago, one of my colleagues introduced me to Streamlit. It was a great concept but far from masterfully executed. To prove a point, I quickly assembled a proof-of-concept using Python, Vue, and WebSockets and wrote a Medium article about it. The mechanics were similar to Streamlit’s, but it was much faster.
I didn’t intend to compete against Streamlit — I just wanted to push things forward. In my quest for faster data apps, I also profiled Streamlit’s code and suggested the improvements that would become their faster_reruns feature.
For a while, that was it. But a few months later, I was waiting to board a flight at London Stansted Airport. Perhaps inspired by jet fuel inhalation, I thought to myself, I want to turn this proof-of-concept into a reality. But I don’t want to build what others have already built and make it marginally better. I want to change the game.
Evolving the Data Apps Ecosystem
At a high level, Streamsync was designed to stand out on two fronts when benchmarked against competitors.
Fast. Lower response times.
- Streamsync enables significantly lower response times when compared to Streamlit.
- It only runs the user script once.
- It uses WebSockets to keep frontend and backend states in sync.
Neat. A creation journey you can enjoy, an end result that looks good inside and out.
- Streamsync uses state-driven, reactive user interfaces. The user interface of a data app is strictly separated from its logic.
- It uses a consistent yet customisable UI design system.
- No caching is needed; things remain in memory. You can use globals and module attributes to store app-wide data.
- Predictable flow of execution. Event handlers are plain, easily testable Python functions. No reruns, no strange decorators.
How fast, though?
Responding to a simple event takes ~1–2ms; I tested this on my MacBook Air M2. This figure is end-to-end; it includes event generation, event handling, and DOM manipulation. However, if you’re running an app remotely, you’ll be limited by the connection speed.
Why a Visual User Interface Editor?
There are many advantages if executed well.
- A visual editor lets you focus on the logic of your application — keeping things neat and the codebase small.
- Complex layouts become trivial to build and maintain while remaining independent from the logic.
- It’s easier to discover and understand the components available.
- Instant visual feedback allows you to create better interfaces.
Not all visual editors are created equal, however.
If they’re poorly executed, visual editors make you wish you’d have stuck to code. Often, while being built, the interface doesn’t look the same as when running the app. Streamsync Builder is different. It was purpose-built from scratch and works as an overlay of the actual app, allowing you to modify the app while it’s running. The app remains fully interactive.
Streamsync Builder works locally. You can use your preferred code editor, plugins, and version control tools. When a change is made to the Python files, the app’s process will be reloaded. A built-in editor for the app’s entry point, main.py, is provided for convenience and is ideal for quick edits, but you don’t need to rely on it.
Architecture
Streamsync is built using Python, TypeScript, Vue, and FastAPI. It’s distributed in a standard Python package.
Overview
- When an event occurs, it’s sent from the browser to the web server using WebSockets.
- The web server (FastAPI) passes it to the framework’s AppRunner via an asyncio call.
- The AppRunner dispatches it to the AppProcess, an isolated process in charge of running the app’s code via a multiprocessing.Pipe.
- The AppProcess finds the appropriate event handler and runs it in a thread pool, using the application state for the relevant session.
State-driven, reactive user interfaces
As mentioned earlier, Streamsync uses state-driven, reactive user interfaces. It was inspired by modern frontend frameworks.
State references in component properties are made with the @{my_var} syntax. For example, the Text property of a Heading component can be set to @{title}. When title mutates, the Heading is automatically updated.
The user interface is detached from the logic. You can replace the Heading for a Text component or show title in as many components as you want — without any changes to the backend.
Input components can use two-way binding — effortlessly keeping them in sync with the state. For example, when a Slider Input changes, the value of my_var changes. When my_var changes, the slider is automatically moved to reflect the change.
Event handlers
State mutations are performed by event handlers, which are assigned to UI components using Streamsync Builder.
The following event handler changes the value of title in the session’s application state, passed as an argument.
def handle_click(state):
state["title"] = "You've clicked the button"
More complex event handlers take a payload, which will be different depending on the event. For example, a handler for Webcam Capture will receive a binary file-like object with a PNG image. In the background, Streamsync takes care of serialisation and deserialisation.
Looking Ahead
Streamsync has mostly been my work, carried out part-time during the last eight months. I’ve been joined by a low-key data scientist who didn’t want his name published and by an early collaborator — thanks, Francisco Edilton.
Now that we have a fully-featured framework with a visual editor and 35 components, the focus will be on sharing Streamsync with people and hearing their thoughts during the next few months. Please feel free to discuss and submit issues.
In terms of features, these are some of the areas we’ll focus on during the next few months:
- Replacing the basic DataFrame component with a high-performance data grid that supports sorting, events, and editions.
- Refining input components.
- Improving observability.
Try Streamsync
Should you replace everything for Streamsync? Maybe not yet. But you should try it and keep an eye on it, especially if you work for Snowflake 😉. It’ll only get better.
Docs are available at this link. The Python package is on PyPi, which means you can install it via pip.
# Install with optional dependencies (pandas and plotly-express)
# Requires Python 3.9.2 or higher
pip install "streamsync[ds]"
# Create the demo app in the "hello" folder
# and launch Builder, which will be accessible via a local URL
streamsync hello
Alternatively, check out the sample app on Google Cloud Run (East US).
The source code is available on GitHub. If you wish to support this project and provide us with extra motivation, please star ⭐ our repository on GitHub.
Introducing Streamsync: No-code Frontend, Python Backend was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.