Skip to content

Commit ab3dcd0

Browse files
author
deeleeramone
committed
more docs pages updates
1 parent 32397f6 commit ab3dcd0

52 files changed

Lines changed: 3794 additions & 1437 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pywry/docs/docs/guides/app-show.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# The `app.show()` Method
1+
# The app.show() Method
22

33
`app.show()` is the primary entry point for rendering content in PyWry. It accepts HTML (as a string or `HtmlContent` object), determines the correct rendering path for the current environment, and returns a handle you can use to interact with the result.
44

@@ -36,7 +36,7 @@ app.show(
3636

3737
## Parameters in Detail
3838

39-
### `content` — what to render
39+
### content — what to render
4040

4141
Accepts either a plain HTML string or an `HtmlContent` object. When you pass a string, PyWry wraps it in a minimal document structure automatically. When you pass `HtmlContent`, you get control over CSS files, JS files, inline styles, JSON data injection, and hot reload watching. See the [HtmlContent guide](html-content.md) for details.
4242

@@ -58,15 +58,15 @@ content = HtmlContent(
5858
app.show(content)
5959
```
6060

61-
### `title` — window title
61+
### title — window title
6262

6363
Sets the title bar text in native window mode. Defaults to `"PyWry"`. Ignored in notebook and browser modes.
6464

6565
```python
6666
app.show("<h1>Dashboard</h1>", title="Sales Dashboard")
6767
```
6868

69-
### `width` and `height` — dimensions
69+
### width and height — dimensions
7070

7171
In native mode, `width` accepts an integer (pixels) for the OS window size. In notebook mode, `width` can also be a CSS string like `"100%"` or `"500px"`. `height` is always an integer (pixels).
7272

@@ -80,7 +80,7 @@ app.show(html, width="100%", height=400)
8080

8181
Defaults come from `WindowConfig` — 1280×720 unless overridden in your configuration.
8282

83-
### `callbacks` — event handlers
83+
### callbacks — event handlers
8484

8585
A dictionary mapping event names to Python callback functions. These are registered before the content is rendered, so they're ready to receive events immediately.
8686

@@ -99,7 +99,7 @@ app.show(html, callbacks={
9999

100100
See the [Event System guide](events.md) for the full callback signature and registration patterns.
101101

102-
### `include_plotly` and `include_aggrid` — library loading
102+
### include_plotly and include_aggrid — library loading
103103

104104
When `True`, PyWry injects the Plotly.js or AG Grid JavaScript and CSS libraries into the page. You don't need to set these manually if you use `app.show_plotly()` or `app.show_dataframe()` — they set the flags automatically.
105105

@@ -111,7 +111,7 @@ app.show(plotly_html, include_plotly=True)
111111
app.show_plotly(fig)
112112
```
113113

114-
### `label` — window identity
114+
### label — window identity
115115

116116
In `MULTI_WINDOW` mode, the `label` identifies which window to create or update. In `SINGLE_WINDOW` mode, the label is fixed and this parameter is ignored. Labels are used in callbacks to target events back to a specific window.
117117

@@ -127,7 +127,7 @@ def on_filter(data, event_type, label):
127127
app.emit("app:update", {"filter": data}, "chart") # target the chart window
128128
```
129129

130-
### `watch` — hot reload
130+
### watch — hot reload
131131

132132
When `True`, PyWry watches the CSS and JS files referenced by `HtmlContent.css_files` and `HtmlContent.script_files` for changes, and live-reloads them in the browser without a full page refresh.
133133

@@ -142,7 +142,7 @@ app.show(content, watch=True) # or override here
142142

143143
See the [Hot Reload guide](hot-reload.md) for details.
144144

145-
### `toolbars` and `modals` — UI chrome
145+
### toolbars and modals — UI chrome
146146

147147
Lists of `Toolbar` or `Modal` objects (or their dict equivalents) that wrap your content in an interactive layout. Toolbars are positioned around the content area; modals are hidden until triggered by events.
148148

@@ -193,7 +193,7 @@ All implement the `BaseWidget` protocol, so `emit()` and `on()` work identically
193193

194194
`app.show()` is the general-purpose method. PyWry also provides specialized variants that configure the right options automatically:
195195

196-
### `app.show_plotly(figure, ...)`
196+
### app.show_plotly(figure, ...)
197197

198198
Converts a Plotly figure to HTML, injects Plotly.js, and pre-wires chart events (`plotly:click`, `plotly:hover`, `plotly:selected`, `plotly:relayout`).
199199

@@ -204,7 +204,7 @@ fig = px.scatter(x=[1, 2, 3], y=[1, 4, 9])
204204
app.show_plotly(fig, callbacks={"plotly:click": on_click})
205205
```
206206

207-
### `app.show_dataframe(data, ...)`
207+
### app.show_dataframe(data, ...)
208208

209209
Converts a DataFrame to AG Grid HTML, injects the AG Grid library, and pre-wires grid events (`grid:cell-clicked`, `grid:selection-changed`, `grid:cell-edited`).
210210

@@ -224,14 +224,14 @@ Both methods accept all the same optional parameters as `app.show()` (`title`, `
224224
```mermaid
225225
flowchart TD
226226
A["app.show(content)"] --> B{"mode = BROWSER?"}
227-
B -- Yes --> C["InlineWidget\nFastAPI + WebSocket\nopens system browser"]
227+
B -- Yes --> C["InlineWidget<br>FastAPI + WebSocket<br>opens system browser"]
228228
B -- No --> D{"In a notebook?"}
229-
D -- Yes --> E{"include_plotly\nor include_aggrid?"}
230-
E -- Yes --> F["InlineWidget\nIFrame + WebSocket\nspecialized JS"]
231-
E -- No --> G{"anywidget\navailable?"}
232-
G -- Yes --> H["PyWryWidget\nanywidget + traitlet sync"]
233-
G -- No --> I["InlineWidget\nIFrame + WebSocket\nfallback"]
234-
D -- No --> J["NativeWindowHandle\nPyTauri + OS webview"]
229+
D -- Yes --> E{"include_plotly<br>or include_aggrid?"}
230+
E -- Yes --> F["InlineWidget<br>IFrame + WebSocket<br>specialized JS"]
231+
E -- No --> G{"anywidget<br>available?"}
232+
G -- Yes --> H["PyWryWidget<br>anywidget + traitlet sync"]
233+
G -- No --> I["InlineWidget<br>IFrame + WebSocket<br>fallback"]
234+
D -- No --> J["NativeWindowHandle<br>PyTauri + OS webview"]
235235
```
236236

237237
You can force a specific mode by setting it on the app:
Lines changed: 132 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,123 @@
11
# Browser Mode
22

3-
Browser mode runs PyWry as a web server, opening content in your default browser instead of native windows.
3+
Browser mode runs PyWry without native OS windows. Instead, it starts a local web server and opens your content in the system browser. All communication between Python and JavaScript happens over WebSocket.
44

5-
## When to Use
5+
## When to Use Browser Mode
66

7-
- **SSH/Remote access** — No GUI available
8-
- **Cross-platform sharing** — Share via URL
9-
- **Development** — Use browser DevTools
7+
| Scenario | Why browser mode helps |
8+
|:---|:---|
9+
| SSH / remote server | No display server available |
10+
| Docker / CI containers | No GUI libraries installed |
11+
| WSL | Native windows can't reach the Linux display |
12+
| Cross-browser testing | Use browser DevTools directly |
13+
| Sharing with others | Send a URL instead of installing an app |
1014

11-
## Enable Browser Mode
15+
## Enabling Browser Mode
1216

13-
### Programmatic
17+
### In Code
1418

1519
```python
1620
from pywry import PyWry, WindowMode
1721

1822
app = PyWry(mode=WindowMode.BROWSER)
1923
handle = app.show("<h1>Hello Browser!</h1>")
20-
21-
app.block() # Keep server running
24+
app.block() # Keeps the server running
2225
```
2326

24-
### Environment Variable
27+
### Via Environment Variable
2528

2629
```bash
2730
export PYWRY_WINDOW__MODE=browser
2831
python my_app.py
2932
```
3033

34+
### Via Config File
35+
36+
```toml
37+
# pywry.toml
38+
[window]
39+
mode = "browser"
40+
```
41+
3142
## How It Works
3243

33-
1. PyWry starts a FastAPI server with WebSocket support
34-
2. Content is served at `http://localhost:8765/widget/{label}`
35-
3. Browser opens automatically
36-
4. WebSocket maintains bidirectional communication
44+
When you call `app.show()` in browser mode:
45+
46+
1. **Server starts** — A FastAPI + uvicorn server launches in a background daemon thread (once, on first `show()`)
47+
2. **Widget registers** — The rendered HTML is stored in the server's widget registry, keyed by label
48+
3. **Browser opens** — The system default browser navigates to `http://127.0.0.1:8765/widget/{label}`
49+
4. **WebSocket connects** — The page's JavaScript bridge opens a WebSocket back to the server for bidirectional events
50+
5. **Events flow**`window.pywry.emit()` in the browser sends JSON over WebSocket to Python; `handle.emit()` in Python pushes JSON back
51+
52+
### Server Routes
53+
54+
| Route | Method | Description |
55+
|:---|:---|:---|
56+
| `/widget/{widget_id}` | GET | Serves the widget's HTML page |
57+
| `/ws/{widget_id}` | WebSocket | Bidirectional event bridge |
58+
| `/health` | GET | Health check (requires internal API token) |
59+
| `/register_widget` | POST | Re-register a widget (kernel restart recovery) |
60+
| `/disconnect/{widget_id}` | POST | Clean disconnect of a widget |
3761

3862
## Server Configuration
3963

40-
### pywry.toml
64+
### Default Settings
4165

42-
```toml
43-
[server]
44-
host = "127.0.0.1"
45-
port = 8765
46-
auto_start = true
66+
| Setting | Default | Environment variable |
67+
|:---|:---|:---|
68+
| Host | `127.0.0.1` | `PYWRY_SERVER__HOST` |
69+
| Port | `8765` | `PYWRY_SERVER__PORT` |
70+
| Widget URL prefix | `/widget` | `PYWRY_SERVER__WIDGET_PREFIX` |
71+
| Auto-start | `True` | `PYWRY_SERVER__AUTO_START` |
72+
73+
### Custom Host and Port
74+
75+
```python
76+
import os
77+
78+
os.environ["PYWRY_SERVER__HOST"] = "0.0.0.0" # Bind to all interfaces
79+
os.environ["PYWRY_SERVER__PORT"] = "9000"
80+
81+
from pywry import PyWry, WindowMode
82+
83+
app = PyWry(mode=WindowMode.BROWSER)
84+
handle = app.show("<h1>Available on the network</h1>")
85+
app.block()
4786
```
4887

49-
### Environment Variables
88+
Or in a config file:
5089

51-
```bash
52-
export PYWRY_SERVER__HOST=0.0.0.0
53-
export PYWRY_SERVER__PORT=8080
90+
```toml
91+
# pywry.toml
92+
[server]
93+
host = "0.0.0.0"
94+
port = 9000
5495
```
5596

5697
## Multiple Widgets
5798

58-
Each widget gets its own URL:
99+
Each widget gets its own URL. The server can serve many widgets simultaneously:
59100

60101
```python
61-
from pywry import PyWry, WindowMode
62-
63102
app = PyWry(mode=WindowMode.BROWSER)
64103

65-
h1 = app.show("<h1>Widget 1</h1>", label="widget-1")
66-
h2 = app.show("<h1>Widget 2</h1>", label="widget-2")
104+
h1 = app.show("<h1>Chart</h1>", label="chart")
105+
h2 = app.show("<h1>Table</h1>", label="table")
67106

68-
# URLs:
69-
# http://localhost:8765/widget/widget-1
70-
# http://localhost:8765/widget/widget-2
107+
# Two browser tabs open:
108+
# http://127.0.0.1:8765/widget/chart
109+
# http://127.0.0.1:8765/widget/table
110+
print(h1.url) # Full URL for the chart widget
111+
print(h2.url) # Full URL for the table widget
71112

72113
app.block()
73114
```
74115

75-
## HTTPS Configuration
116+
## HTTPS
117+
118+
Enable TLS by providing certificate and key files:
76119

77-
Enable HTTPS via configuration:
120+
### Via Config
78121

79122
```toml
80123
# pywry.toml
@@ -85,22 +128,72 @@ ssl_certfile = "/path/to/cert.pem"
85128
ssl_keyfile = "/path/to/key.pem"
86129
```
87130

88-
Or environment variables:
131+
### Via Environment Variables
89132

90133
```bash
91134
export PYWRY_SERVER__SSL_CERTFILE=/path/to/cert.pem
92135
export PYWRY_SERVER__SSL_KEYFILE=/path/to/key.pem
93136
```
94137

138+
When SSL is configured, widget URLs use `https://` and WebSocket connections use `wss://`.
139+
95140
## WebSocket Security
96141

97-
Enable per-widget authentication tokens:
142+
By default, each widget gets a unique token generated with `secrets.token_urlsafe(32)`. The browser sends this token via the `Sec-WebSocket-Protocol` header during the WebSocket handshake. Connections without a valid token are rejected.
143+
144+
```toml
145+
# pywry.toml
146+
[server]
147+
websocket_require_token = true # Default — enforce per-widget tokens
148+
```
149+
150+
### Origin Validation
151+
152+
Restrict which origins can connect:
98153

99154
```toml
100155
[server]
101-
websocket_require_token = true
156+
websocket_allowed_origins = ["https://myapp.example.com"]
157+
```
158+
159+
An empty list (the default) allows connections from any origin.
160+
161+
### CORS
162+
163+
```toml
164+
[server]
165+
cors_origins = ["https://myapp.example.com"] # Default: ["*"]
166+
```
167+
168+
## Callbacks and Events
169+
170+
Callbacks work identically in browser mode — the WebSocket transport is transparent:
171+
172+
```python
173+
app = PyWry(mode=WindowMode.BROWSER)
174+
175+
def on_click(data, event_type, label):
176+
handle.emit("app:response", {"message": "Button clicked!"})
177+
178+
handle = app.show(
179+
'<button onclick="window.pywry.emit(\'app:click\', {})">Click me</button>',
180+
callbacks={"app:click": on_click},
181+
)
182+
app.block()
102183
```
103184

104-
## Deploy Mode
185+
## Blocking
186+
187+
`app.block()` in browser mode monitors WebSocket connections. It returns when all widgets have disconnected (i.e., every browser tab has been closed or navigated away):
188+
189+
```python
190+
app.show(content)
191+
app.block() # Waits until all browser tabs close
192+
# Code continues after disconnect
193+
```
194+
195+
Press `Ctrl+C` to stop immediately — PyWry calls `app.destroy()` to shut down the server cleanly.
196+
197+
## Relationship to Deploy Mode
105198

106-
For production deployments with multiple workers, see [Deploy Mode](deploy-mode.md).
199+
Browser mode runs the server in a **background thread** alongside your script. For **production** deployments where the server _is_ your application, use [Deploy Mode](deploy-mode.md) instead — it runs uvicorn in the foreground with multi-worker support, Redis state, and proper process management.

pywry/docs/docs/guides/content-assembly.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Here's what happens when `app.show(content)` is called in native window mode (th
2424
6. Assemble final HTML document
2525
7. Send to rendering path (native / notebook / browser)
2626

27-
## What Goes Into `<head>`
27+
## What Goes Into &lt;head&gt;
2828

2929
The `<head>` section is built from these components, injected in this exact order:
3030

@@ -153,7 +153,7 @@ Each file in `HtmlContent.script_files` is injected as a `<script>` tag.
153153

154154
`HtmlContent.init_script` runs last — it's the final `<script>` tag in the document. By the time it executes, everything else is available: `window.pywry`, `window.json_data`, all CSS applied, all libraries loaded.
155155

156-
## What Goes Into `<body>`
156+
## What Goes Into &lt;body&gt;
157157

158158
### Content Wrapping
159159

0 commit comments

Comments
 (0)