A cross-platform embeddable PTY/ConPTY terminal for Jupyter and Python web applications
Project description
Notebook Terminal
A cross-platform embeddable terminal for Jupyter Notebook, JupyterLab, Flask, Django, and custom Python frontends.
Notebook Terminal provides a persistent PTY/ConPTY session with real-time output, optional keyboard input, programmatic command execution, rich output tabs, interactive DataFrame viewing, Plotly support, and native Jupyter widgets.
Version 0.1.1 is an alpha release. Test carefully before production use.
Features
- POSIX PTY support on Linux and macOS
- ConPTY support on Windows through
pywinpty - PowerShell, CMD, Bash, Zsh, and other shells
- Real-time output streaming
- Interactive or read-only terminal mode
- Programmatic commands and process input
- Python code and Python-file execution
- Matplotlib and Seaborn image tabs
- Interactive Plotly tabs
- Interactive DataFrame viewer with filtering, sorting, and pagination
- Native
ipywidgetsthrough the active Jupyter kernel - Closable rich-output tabs
- ANSI colors and status helpers
- Flask WebSocket and Django Channels adapters
- Framework-independent
TerminalSession
Installation
python -m pip install notebook-terminal
Install optional integrations:
python -m pip install "notebook-terminal[flask]"
python -m pip install "notebook-terminal[django]"
python -m pip install "notebook-terminal[data,plotly]"
For local development:
git clone https://github.com/YOUR-USERNAME/notebook-terminal.git
cd notebook-terminal
python -m pip install -e ".[dev]"
Jupyter quick start
from notebook_terminal import terminal
term = terminal(height=450)
Run a command from another cell:
term.run("python --version")
term.run() uses the existing terminal. It does not display a second copy of the widget.
Interactive and managed modes
Interactive terminal:
term = terminal(interactive=True)
Read-only terminal controlled only through Python:
term = terminal(interactive=False)
term.run("python application.py")
You can still send input programmatically:
term.send_text("answer\r\n")
Running commands
term.run("git status")
term.run("pip list")
term.run("python script.py")
Create a separate terminal explicitly:
second = term.run("python another_script.py", new_window=True)
Running Python code
term.run_python("""
import time
for value in range(5):
print(value, flush=True)
time.sleep(1)
""")
Run an existing file:
term.run_python_file(
"analysis.py",
args=["input.csv", "--limit", "100"],
)
Rich output
Interactive DataFrame
term.run_python("""
import pandas as pd
frame = pd.DataFrame({
"city": ["Warsaw", "Krakow", "Gdansk", "Poznan"],
"value": [12, 18, 9, 15],
})
display(frame)
""", rich_output=True, clear_previous=True)
The DataFrame tab supports filtering, sorting, pagination, page-size selection, and horizontal scrolling.
Matplotlib
term.run_python("""
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4], [3, 7, 4, 8], marker="o")
plt.title("Example chart")
plt.show()
""", rich_output=True, clear_previous=True)
Seaborn
term.run_python("""
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
frame = pd.DataFrame({
"category": ["A", "B", "C"],
"value": [10, 17, 13],
})
sns.barplot(data=frame, x="category", y="value")
plt.show()
""", rich_output=True, clear_previous=True)
Plotly
term.run_python("""
import pandas as pd
import plotly.express as px
frame = pd.DataFrame({
"category": ["A", "B", "C", "D"],
"value": [10, 17, 13, 22],
})
figure = px.bar(frame, x="category", y="value", title="Interactive Plotly chart")
display(figure)
""", rich_output=True, clear_previous=True)
Native Jupyter widgets
Jupyter widgets need the current notebook kernel and its Comm connection. Use run_kernel():
term.run_kernel("""
import ipywidgets as widgets
from IPython.display import display
slider = widgets.IntSlider(value=25, min=0, max=100, description="Value:")
label = widgets.Label()
slider.observe(
lambda change: setattr(label, "value", f"Selected: {change['new']}"),
names="value",
)
display(widgets.VBox([slider, label]))
""", clear_previous=True)
For interactive Matplotlib, install ipympl and use %matplotlib widget inside run_kernel().
Styled messages
term.success("Code completed successfully")
term.error("Operation failed")
term.warning("Configuration file is missing")
term.info("Starting analysis")
term.debug("Current value: 42")
Custom styling:
from notebook_terminal import Color
term.write_line(
"Custom message",
color=Color.GREEN,
bold=True,
icon="→",
)
Styled messages are emitted directly to the terminal output channel. They are not written to shell input, so PowerShell does not try to execute ANSI reset sequences such as [0m.
Copy and paste
In interactive mode:
- select text and press
Ctrl+Cto copy - press
Ctrl+Cwithout a selection to interrupt the active process - press
Ctrl+Vto paste - use the mouse wheel to scroll
In read-only mode, copying remains available, but keyboard input is not forwarded to the process.
Process control
term.send_text("answer\r\n")
term.interrupt()
term.clear()
term.clear(clear_tabs=True)
term.clear_tabs()
term.close_tab(0)
term.focus()
term.close_terminal()
Framework-independent session
from notebook_terminal import TerminalSession
session = TerminalSession()
session.subscribe(
lambda chunk: print(chunk.decode("utf-8", errors="replace"), end="")
)
session.run("python --version")
session.success("Completed")
session.write() sends input to the shell. session.emit_output() publishes display-only output to attached frontends.
Flask
from flask import Flask, render_template
from notebook_terminal.web import flask_blueprint, manager
app = Flask(__name__)
app.register_blueprint(flask_blueprint(), url_prefix="/terminal")
@app.route("/")
def index():
session_id, session = manager.create(cwd=".", interactive=True)
return render_template("terminal.html", session_id=session_id)
Connect an xterm.js frontend to the WebSocket endpoint exposed by the adapter. Programmatic control remains available through the returned session object.
Django
Install Django Channels and add the provided consumer to your ASGI WebSocket routes:
from django.urls import re_path
from notebook_terminal.web import django_consumer
TerminalConsumer = django_consumer()
websocket_urlpatterns = [
re_path(
r"^ws/terminal/(?P<sid>[0-9a-f]+)/$",
TerminalConsumer.as_asgi(),
),
]
Architecture
TerminalSession
|
+-----------------+-----------------+
| | |
Jupyter widget Flask WebSocket Django Channels
| | |
+-----------------+-----------------+
|
PTY / ConPTY
|
PowerShell / CMD / Bash / Zsh
Security
An embedded terminal executes commands with the permissions of the hosting Python process. Do not expose unrestricted terminal access publicly without authentication, authorization, session ownership checks, process limits, automatic cleanup, and operating-system or container isolation.
Development and publishing
See PUBLISHING.md for complete build, GitHub, TestPyPI, and PyPI instructions.
License
MIT License. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file notebook_terminal-0.1.1.tar.gz.
File metadata
- Download URL: notebook_terminal-0.1.1.tar.gz
- Upload date:
- Size: 1.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11dd913f19700ca7071483fc05c1015cf69fc582d923ea4996378ba701f56112
|
|
| MD5 |
f15a84c69aca3a60a907e176ee50bb61
|
|
| BLAKE2b-256 |
050ec0ecfa1c84bf48fbcf0f5d00b321175f3f924a6cf841d31283b2e1747fa8
|
File details
Details for the file notebook_terminal-0.1.1-py3-none-any.whl.
File metadata
- Download URL: notebook_terminal-0.1.1-py3-none-any.whl
- Upload date:
- Size: 1.6 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74b92f1a67adc0f4c49783e15b3903262114a5ded89c62eb65de00fe5babbc61
|
|
| MD5 |
fc7df6607222a9889c394a44513cb8b4
|
|
| BLAKE2b-256 |
29131334bf46762e1a4831252fdd9a4b08da2fcdffb78c2d5030958a9710f18a
|