$git clone https://github.com/deeleeramone/PyWryPyWry is a cross-platform app factory, rendering engine and UI toolkit for Python that produces native desktop, web, and notebook experiences from a single API.
| 1 | <div align="center"> |
| 2 | |
| 3 | <picture> |
| 4 | <source media="(prefers-color-scheme: dark)" srcset="pywry/pywry/frontend/assets/PyWry-dark.svg"> |
| 5 | <img src="pywry/pywry/frontend/assets/PyWry-light.svg" alt="PyWry" width="640"> |
| 6 | </picture> |
| 7 | |
| 8 | </div> |
| 9 | |
| 10 | PyWry is a cross-platform rendering engine and desktop UI toolkit for Python. One API, three output targets: |
| 11 | |
| 12 | - **Native window** — OS webview via [PyTauri](https://pypi.org/project/pytauri/). Not Qt, not Electron. Use unrestricted HTML/CSS/JS. |
| 13 | - **Jupyter widget** — anywidget + FastAPI + WebSocket, works in JupyterLab, VS Code, and Colab. |
| 14 | - **Browser tab** — FastAPI server with Redis state backend for horizontal scaling. |
| 15 | |
| 16 | **Build Once, Render Anywhere:** Prototype interactive data apps in a Jupyter Notebook, easily deploy them as web apps, and seamlessly compile them into secure, lightweight standalone desktop executables via `pywry[freeze]`. |
| 17 | |
| 18 | <div align="center"> |
| 19 | |
| 20 |  |
| 21 | |
| 22 | </div> |
| 23 | |
| 24 | ## Installation |
| 25 | |
| 26 | Python 3.10–3.14, virtual environment recommended. |
| 27 | |
| 28 | ```bash |
| 29 | pip install pywry |
| 30 | ``` |
| 31 | |
| 32 | Core extras: |
| 33 | |
| 34 | | Extra | When to use | |
| 35 | |-------|-------------| |
| 36 | | `pip install 'pywry[notebook]'` | Jupyter / anywidget integration | |
| 37 | | `pip install 'pywry[auth]'` | OAuth2 and keyring-backed auth support | |
| 38 | | `pip install 'pywry[freeze]'` | PyInstaller hook for standalone executables | |
| 39 | | `pip install 'pywry[mcp]'` | Model Context Protocol server support | |
| 40 | | `pip install 'pywry[sqlite]'` | Encrypted SQLite state backend (SQLCipher) | |
| 41 | | `pip install 'pywry[all]'` | Everything above | |
| 42 | |
| 43 | Chat provider extras: |
| 44 | |
| 45 | | Extra | When to use | |
| 46 | |-------|-------------| |
| 47 | | `pip install 'pywry[openai]'` | `OpenAIProvider` (OpenAI SDK) | |
| 48 | | `pip install 'pywry[anthropic]'` | `AnthropicProvider` (Anthropic SDK) | |
| 49 | | `pip install 'pywry[magentic]'` | `MagenticProvider` (any magentic-supported LLM) | |
| 50 | | `pip install 'pywry[acp]'` | `StdioProvider` (Agent Client Protocol subprocess) | |
| 51 | | `pip install 'pywry[deepagent]'` | `DeepagentProvider` (LangChain Deep Agents — includes MCP adapters and ACP) | |
| 52 | |
| 53 | The chat UI itself is included in the base package. Provider extras only install the matching third-party SDK. |
| 54 | |
| 55 | **Linux only** — install system webview dependencies first: |
| 56 | |
| 57 | ```bash |
| 58 | sudo apt-get install libwebkit2gtk-4.1-dev libgtk-3-dev libglib2.0-dev \ |
| 59 | libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \ |
| 60 | libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 \ |
| 61 | libxcb-shape0 libgl1 libegl1 |
| 62 | ``` |
| 63 | |
| 64 | ## Quick Start |
| 65 | |
| 66 | ```python |
| 67 | from pywry import PyWry |
| 68 | |
| 69 | app = PyWry() |
| 70 | app.show("Hello World!") |
| 71 | app.block() |
| 72 | ``` |
| 73 | |
| 74 | ### Toolbar + callbacks |
| 75 | |
| 76 | ```python |
| 77 | from pywry import PyWry, Toolbar, Button |
| 78 | |
| 79 | app = PyWry() |
| 80 | |
| 81 | def on_click(data, event_type, label): |
| 82 | app.emit("pywry:set-content", {"selector": "h1", "text": "Clicked!"}, label) |
| 83 | |
| 84 | app.show( |
| 85 | "<h1>Hello</h1>", |
| 86 | toolbars=[Toolbar(position="top", items=[Button(label="Click me", event="app:click")])], |
| 87 | callbacks={"app:click": on_click}, |
| 88 | ) |
| 89 | app.block() |
| 90 | ``` |
| 91 | |
| 92 | ### Pandas DataFrame → AgGrid |
| 93 | |
| 94 | ```python |
| 95 | from pywry import PyWry |
| 96 | import pandas as pd |
| 97 | |
| 98 | app = PyWry() |
| 99 | df = pd.DataFrame({"name": ["Alice", "Bob", "Carol"], "age": [30, 25, 35]}) |
| 100 | |
| 101 | def on_select(data, event_type, label): |
| 102 | names = ", ".join(row["name"] for row in data["rows"]) |
| 103 | app.emit("pywry:alert", {"message": f"Selected: {names}"}, label) |
| 104 | |
| 105 | app.show_dataframe(df, callbacks={"grid:row-selected": on_select}) |
| 106 | app.block() |
| 107 | ``` |
| 108 | |
| 109 | ### Plotly chart |
| 110 | |
| 111 | ```python |
| 112 | from pywry import PyWry |
| 113 | import plotly.express as px |
| 114 | |
| 115 | app = PyWry(theme="light") |
| 116 | fig = px.scatter(px.data.iris(), x="sepal_width", y="sepal_length", color="species") |
| 117 | app.show_plotly(fig) |
| 118 | app.block() |
| 119 | ``` |
| 120 | |
| 121 | ## Features |
| 122 | |
| 123 | - **Toolbar components** — `Button`, `Select`, `MultiSelect`, `TextInput`, `SecretInput`, `SliderInput`, `RangeInput`, `Toggle`, `Checkbox`, `RadioGroup`, `TabGroup`, `Marquee`, `Modal`, and more. All Pydantic models; position them around the content edges or inside the chart area. |
| 124 | - **Two-way events** — `app.emit()` and `app.on()` bridge Python and JavaScript in both directions. Pre-wired Plotly and AgGrid events included. |
| 125 | - **Chat** — streaming chat widget with threads, slash commands, artifacts, and pluggable providers: `OpenAIProvider`, `AnthropicProvider`, `MagenticProvider`, `CallbackProvider`, `StdioProvider` (ACP subprocess), and `DeepagentProvider` (LangChain Deep Agents). |
| 126 | - **TradingView charts** — extended Lightweight Charts integration with a full drawing surface (trendlines, fib tools, text annotations, price notes, brushes), pluggable datafeed API, UDF adapter for external quote servers, streaming bar updates, compare overlays, compare-derivative indicators (Spread / Ratio / Sum / Product / Correlation), savable layouts, and a themeable settings panel. |
| 127 | - **Theming** — light / dark / system modes, themeable via `--pywry-*` CSS variables, hot reload during development. |
| 128 | - **Security** — token auth, CSP headers, `SecuritySettings.strict()` / `.permissive() |