๐ JupyterPilot
Enterprise-grade AI coding partner & secure multi-team notebook isolation for JupyterHub
One Hub. Many teams. Every user isolated. AI in every cell.
๐ Table of Contents
| Section | What you'll learn |
|---|---|
| ๐บ๏ธ What is JupyterPilot? | High-level purpose and design philosophy |
| ๐๏ธ Architecture: 1 Hub โ Many Worker EC2s | How a single hub routes 100s of users across multiple EC2s |
| ๐ฅ Use Case 1 โ Multi-Team EC2 Routing | Group A on EC2-1, Group B on EC2-2, automatically |
| ๐ Use Case 2 โ Per-User Isolation & RBAC | Each user gets their own OS account, process & resource cap |
| ๐ค Use Case 3 โ AI Magic Commands | %do, %fix, %review, %rework inside notebooks |
| ๐ Use Case 4 โ Vault Secret Injection | Per-user API keys injected at spawn, zero disk I/O |
| ๐ Use Case 5 โ Live Monitoring Dashboard | Real-time CPU/RAM/network for every worker EC2 |
| ๐ Use Case 6 โ Crash Recovery | Hub restarts do not lose running user sessions |
| ๐ ๏ธ Installation Guide | Full step-by-step Hub & Worker EC2 setup |
| ๐งช Running Tests | Full test suite, no real SSH or AWS needed |
| ๐บ๏ธ Roadmap | What is built and what is next |
๐บ๏ธ What is JupyterPilot?
JupyterPilot solves two problems that every data team running JupyterHub eventually hits:
Problem 1 โ "How do we run notebooks for 100+ users without one user crashing everyone else?"
JupyterPilot's SSH spawner teleports each user's notebook server onto an isolated EC2 worker, wraps it in cgroup resource limits, and tracks all sessions in SQLite so nothing is lost if the Hub reboots.
Problem 2 โ "How do we get AI coding assistance inside notebooks without a brittle plugin?"
JupyterPilot's IPython extension adds four magic commands (
%do,%fix,%review,%rework) that speak to any LLM backend โ local Ollama, OpenAI, Claude, Gemini, or an MCP server โ and inject clean Python directly into the next cell.
๐๏ธ Architecture: 1 Hub โ Many Worker EC2s
This is the core deployment model JupyterPilot is built for.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Hub EC2 (always on) โ
โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โ โ JupyterHub โ โ SQLite DB โ โ
โ โ + Google โโโโถโ team_mappings โ โ
โ โ OAuth โ โ user_sessions โ โ
โ โโโโโโโโฌโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โ โ CustomSpawner โ
โโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ-โโ
โ SSH (Paramiko, per spawn)
โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Worker EC2 โ group_a โ โ Worker EC2 โ group_b โ
โ IP: 10.0.1.10 โ โ IP: 10.0.1.20 โ
โ โ โ โ
โ alice โ port 8901 โ โ carol โ port 8901 โ
โ bob โ port 8902 โ โ dave โ port 8902 โ
โ ... โ โ ... โ
โ (up to 50 users) โ โ (up to 50 users) โ
โ โ โ โ
โ metrics_agent.py โโWSโโโโผโโโโโโโโโโโผโโโถ /monitoring/ws โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Key properties:
- Each group maps to exactly one worker EC2 โ all group members share that machine
- Each user on a worker gets their own OS account, process, and TCP port โ completely isolated
- The Hub uses SQLite to remember who is on which EC2 โ survives hub restarts
- Worker EC2s send live metrics back to the hub's monitoring dashboard over WebSocket
- Adding a new worker EC2 requires zero hub restarts โ just insert a row in SQLite
๐ฅ Use Case 1 โ Multi-Team EC2 Routing
Goal: Group A's 50 users run on EC2-1. Group B's 50 users run on EC2-2. The Hub routes automatically.
How it works
When a user logs in, the spawner:
- Reads the user's JupyterHub group (e.g.,
group_a) - Looks up
group_ain the SQLiteteam_mappingstable โ gets10.0.1.10 - SSHes into
10.0.1.10as that user - Launches
jupyterhub-singleuseron a free port - Stores the session in SQLite (
username,vm_ip,port,group_name)
The Hub's proxy then routes https://hub.yourco.com/user/alice/ โ 10.0.1.10:8901.
Setup
Step 1 โ Define your groups in user_mapping.json:
{
"group_a": {
"server_ip": "10.0.1.10",
"server_ssh_key": "/etc/jupyterhub/keys/group_a.pem",
"admin_ssh_user": "ubuntu"
},
"group_b": {
"server_ip": "10.0.1.20",
"server_ssh_key": "/etc/jupyterhub/keys/group_b.pem",
"admin_ssh_user": "ubuntu"
},
"group_c": {
"server_ip": "10.0.1.30",
"server_ssh_key": "/etc/jupyterhub/keys/group_c.pem",
"admin_ssh_user": "ubuntu"
}
}
Step 2 โ Seed the routing table into SQLite (run once on deploy):
python -m jupyterpilot.seed_sqlite \
--db /var/lib/jupyterhub/jupyterpilot_state.db \
--mapping /etc/jupyterhub/user_mapping.json
# Output:
# โ group_a โ 10.0.1.10
# โ group_b โ 10.0.1.20
# โ group_c โ 10.0.1.30
# [DONE] Seeded 3 team(s), skipped 0.
Step 3 โ Assign users to groups in the JupyterHub Admin Panel:
JupyterHub Admin โ Groups โ group_a โ Add Users: alice, bob, charlie, ...
JupyterHub Admin โ Groups โ group_b โ Add Users: carol, dave, eve, ...
That's it. From this point on:
alicelogs in โ spawned on10.0.1.10carollogs in โ spawned on10.0.1.20- Neither can see or affect the other
Adding a new worker EC2 without any downtime
# On the Hub VM โ no JupyterHub restart needed:
python3 - <<'EOF'
from jupyterpilot.session_store import SessionStore
store = SessionStore("/var/lib/jupyterhub/jupyterpilot_state.db")
store.set_mapping("group_d", "10.0.1.40", "/etc/jupyterhub/keys/group_d.pem", "ubuntu")
print("group_d is live!")
EOF
โ See full AWS deployment guide
๐ Use Case 2 โ Per-User Isolation & RBAC
Goal: On a shared EC2, user A's notebook cannot see user B's files, processes, or memory. Admins can manage any session; regular users can only manage their own.
Per-User OS Isolation
Every new user is automatically provisioned on the worker EC2 on their first login (Just-In-Time provisioning). No manual adduser needed:
alice logs in for the first time
โ
spawner calls _provision_user_jit()
โ SSH as admin (ubuntu) to 10.0.1.10
โ sudo adduser --disabled-password alice
โ sudo loginctl enable-linger alice โ keeps process alive after SSH closes
โ sudo mkdir /home/alice/notebook
โ sudo pip3 install jupyterhub notebook jupyterpilot[ai]
โ copy Hub SSH key โ /home/alice/.ssh/authorized_keys
โ
spawner SSHes as alice, finds a free port, launches singleuser
After provisioning:
aliceowns/home/alice/โ Bob cannot read italice's notebook process runs as OS useraliceโ Bob's kernel cannot signal it- Each process is wrapped in a cgroup scope โ memory/CPU cannot bleed across
cgroups v2 Resource Limits
Configure per-user hard limits in hub_settings.json:
{
"resource_limits": {
"memory_max": "2G",
"cpu_quota": "100%"
}
}
The spawner automatically wraps the launch command with systemd-run:
# What actually runs on the worker EC2:
systemd-run --user --scope \
--unit=jupyterhub-alice-1722000000 \
--property=MemoryMax=2G \
--property=CPUQuota=100% \
-- nohup jupyterhub-singleuser --ip=0.0.0.0 --port=8901 ...
If Alice's notebook eats 2.1GB of RAM, the kernel OOM-kills her process โ not Bob's, not the hub's.
โ ๏ธ Required on each worker EC2:
sudo loginctl enable-linger <username>for every user, otherwise Ubuntu kills the process 7 seconds after the SSH session closes. JIT provisioning handles this automatically.
2-Tier RBAC
| Role | What they can do | How to grant |
|---|---|---|
user |
Start/stop/poll their own server only | Default for all users |
admin |
Start/stop/poll any user's server | JupyterHub Admin Panel โ Users โ โ Make Admin |
No code changes, no restarts โ promote a user to admin live via the UI.
๐ค Use Case 3 โ AI Magic Commands
Goal: Data scientists get an AI pair-programmer that understands their notebook context, fixes their errors, and refactors their code โ all without leaving Jupyter.
Load the extension
# In any Jupyter cell:
%load_ext jupyterpilot.extension
# โ
JupyterPilot loaded. Available: %do, %fix, %review, %rework
Or auto-load in every session:
mkdir -p ~/.ipython/profile_default/startup/
echo "%load_ext jupyterpilot.extension" > ~/.ipython/profile_default/startup/00-jupyterpilot.ipy
%do โ Generate code from natural language
%do load the CSV at data/sales.csv, parse the date column, and plot monthly revenue
JupyterPilot injects into the next cell:
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('data/sales.csv', parse_dates=['date'])
df['month'] = df['date'].dt.to_period('M')
monthly = df.groupby('month')['revenue'].sum()
monthly.plot(kind='bar', title='Monthly Revenue', figsize=(12, 5))
plt.tight_layout()
plt.show()
Run immediately instead of injecting:
%do --run create a sample dataframe with 100 rows and columns name, age, score
The AI always sees the last 3 cells of your notebook as context โ it knows what variables you have already defined.
%fix โ Auto-heal the last exception
# You ran this and it crashed:
df.groupby('date')['revenue'].sum().plot()
# KeyError: 'date'
# Just run:
%fix
# ๐ง JupyterPilot โ analysing error and generating fix โฆ
# Injects into next cell:
df['date'] = pd.to_datetime(df['date'])
df.groupby('date')['revenue'].sum().plot()
%fix strips all JupyterHub/IPython internal stack frames before sending to the LLM โ only your code's error reaches the model.
%review โ AST-aware code quality review
%review # review last cell
%review -n 3 # review last 3 cells
%review --all # review the entire notebook session
============================================================
๐ JupyterPilot Code Review
============================================================
๐ Static Analysis (AST):
- Line 3: bare `except:` โ catches all exceptions including KeyboardInterrupt
- Line 7: `process_data()` missing return type annotation
- Line 7: argument `df` in `process_data()` missing type hint
๐ค LLM Review:
โข Overall: The code works but lacks defensive programming and type safety.
โข Use `except Exception as e:` instead of bare `except`
โข Add type hint `df: pd.DataFrame` and return `-> pd.DataFrame`
โข Consider extracting date parsing into a helper for reusability
โข No security concerns found
============================================================
%rework โ LLM-powered refactoring
%rework add type hints and a docstring
%rework --diff convert this to method chaining # preview before applying
With --diff:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Proposed changes (use %rework without --diff to apply):
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
--- original
+++ reworked
-def process(df):
- result = df[df['age'] > 18]
- result = result.groupby('city')['score'].mean()
- return result
+def process(df: pd.DataFrame) -> pd.Series:
+ """Return mean score by city for adult users."""
+ return (
+ df[df['age'] > 18]
+ .groupby('city')['score']
+ .mean()
+ )
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
LLM Backend Configuration (~/.jupyterpilot/config.json)
{
"mode": "local",
"local": {
"url": "http://localhost:11434/api/generate",
"model": "qwen2.5-coder:7b",
"timeout": 30
},
"cloud": {
"provider": "openai",
"model": "gpt-4o-mini",
"api_key": "sk-...",
"timeout": 30
},
"mcp": {
"server_url": "http://localhost:3000",
"tools": ["generate_code"],
"timeout": 30
},
"rate_limit": {
"requests_per_minute": 20
}
}
| Mode | Best for | Notes |
|---|---|---|
local |
Air-gapped teams, low latency | Requires Ollama running on the worker EC2 |
cloud |
Best quality (GPT-4, Claude, Gemini) | Requires API key; costs per token |
mcp |
Custom enterprise tool servers | MCP protocol over HTTP |
๐ Use Case 4 โ Vault Secret Injection
Goal: Each user's notebook automatically gets their personal API keys (OpenAI key, database URL, S3 credentials) injected as environment variables at spawn time โ no secrets on disk, no manual
.envfiles.
How it works
User spawns โ
pre_spawn_hook() โ
VaultClient.get_user_secrets("alice") โ
GET https://vault.internal/v1/secret/data/jupyterpilot/alice โ
{"OPENAI_API_KEY": "sk-alice-...", "DATABASE_URL": "postgres://..."}
โ
self.environment.update(secrets)
โ
JupyterHub passes environment to jupyterhub-singleuser at launch
โ
Alice's kernel: os.environ["OPENAI_API_KEY"] == "sk-alice-..."
Alice's keys are never written to disk. Bob's kernel cannot see Alice's environment.
Setup
# On the Hub VM before starting JupyterHub:
export VAULT_ADDR=http://10.0.0.5:8200
export VAULT_TOKEN=your-policy-scoped-token
// hub_settings.json
{
"vault_enabled": true,
"vault_secret_path": "secret/jupyterpilot"
}
# Store each user's secrets in Vault:
vault kv put secret/jupyterpilot/alice \
OPENAI_API_KEY="sk-alice-..." \
DATABASE_URL="postgres://alice:pass@db.internal/mydb"
vault kv put secret/jupyterpilot/bob \
OPENAI_API_KEY="sk-bob-..." \
S3_BUCKET="bob-data-bucket"
Graceful degradation: If Vault is unreachable, the spawn continues uninterrupted โ Vault is opt-in. A warning is logged, but no exception is raised.
๐ Use Case 5 โ Live Monitoring Dashboard
Goal: The hub admin can see real-time CPU, memory, disk, and network I/O for every connected worker EC2 โ without SSH-ing into anything.
Architecture
Worker EC2 (metrics_agent.py)
โ WebSocket every 2 seconds
Hub EC2 (AgentWebSocketHandler at /monitoring/ws)
โ stores latest snapshot per hostname in _WORKER_SNAPSHOTS
โ broadcasts to all connected browser tabs
Browser (monitoring.html)
โ dark-mode real-time dashboard with per-worker panels
Start the agent on each worker EC2
python3 /opt/jupyterpilot/jupyterpilot/metrics_agent.py \
--hub ws://10.0.0.10:8000/monitoring/ws \
--interval 2
Or as a systemd service so it survives reboots:
# /etc/systemd/system/jupyterpilot-agent.service
[Unit]
Description=JupyterPilot Metrics Agent
After=network.target
[Service]
ExecStart=/usr/bin/python3 /opt/jupyterpilot/jupyterpilot/metrics_agent.py \
--hub ws://10.0.0.10:8000/monitoring/ws --interval 2
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now jupyterpilot-agent
Visit http://<HUB-PUBLIC-IP>:8000/monitoring to see the live dashboard.
What you see per worker EC2:
- Live CPU % (total + per-core) and 1/5/15-min load averages
- RAM: used / total / percent
- Disk: used / total / percent (root filesystem)
- Network: upload/download Mbps (rolling 2-second rate)
Access control:
- All authenticated JupyterHub users can view the dashboard
- Regular users see only their own worker EC2's panel
- Admins see all connected workers
๐ Use Case 6 โ Crash Recovery
Goal: The Hub EC2 reboots at 3am. When it comes back up, every user's running notebook server is automatically detected and re-linked โ no user needs to restart their server.
How SQLite makes this safe
Every spawn writes a row to user_sessions:
username |
vm_ip |
port |
pid |
status |
group_name |
|---|---|---|---|---|---|
alice |
10.0.1.10 |
8901 |
4821 |
running |
group_a |
bob |
10.0.1.10 |
8902 |
4830 |
running |
group_a |
carol |
10.0.1.20 |
8901 |
5012 |
running |
group_b |
The SQLite DB lives at /var/lib/jupyterhub/jupyterpilot_state.db โ outside the Python process. After a Hub restart:
poll()readsvm_ipfrom SQLite (not fromself.ip, which is empty after restart)- SSHes to the worker EC2 and checks if the PID is still alive
- If alive โ
Nonereturned โ Hub re-attaches the proxy โ user session continues - If dead โ
0returned โ JupyterHub prompts user to restart their server
The JSON fallback
If SQLite itself is corrupted or the disk is full, the spawner automatically falls back to user_mapping.json for group-to-EC2 routing. A warning is logged, but no exception is raised and no user loses connectivity.
๐ ๏ธ Installation Guide
The full installation guide lives in a dedicated document โ it covers every step for both the Hub EC2 and each Worker EC2, including SSH key exchange, SQLite seeding, optional Vault secrets, the metrics agent, and a verification checklist.
๐ Read the full Installation Guide โ
Quick outline of what it covers:
| Step | What you are doing |
|---|---|
| Step 1 โ Hub EC2 Setup | Install JupyterPilot, configure hub_settings.json, generate SSH key |
| Step 2 โ Worker EC2 Setup | Install jupyterhub-singleuser, get_port.py, enable lingering |
| Step 3 โ SSH Key Exchange | Paste Hub public key into each worker's authorized_keys |
| Step 4 โ Register Workers | Seed user_mapping.json into SQLite |
| Step 5 โ Start JupyterHub | Assign users to groups, start hub, switch to production auth |
| Step 6 โ Metrics Agent | systemd service for live monitoring on each worker |
| Step 7 โ Vault Secrets | Per-user secret injection at spawn |
| Step 8 โ AI Magic Commands | Auto-load %do, %fix, %review, %rework |
| Verification Checklist | End-to-end smoke tests |
| Troubleshooting | Common errors and fixes |
๐ฆ Repository Structure
JupyterPilot/
โโโ spawner.py # SSH spawner โ RBAC, cgroups, Vault, crash recovery
โโโ jupyterhub_config.py # JupyterHub daemon config
โโโ jupyterpilot_extension.py # Standalone IPython extension entry point
โโโ hub_settings.json # Network, auth, resource limits & Vault config
โโโ user_mapping.json # Worker EC2 routing (seed source / SQLite fallback)
โ
โโโ jupyterpilot/ # Core package
โ โโโ __init__.py # Version & lazy extension loader
โ โโโ admin.py # RBACManager โ 2-tier role enforcement
โ โโโ session_store.py # SQLite: team_mappings + user_sessions tables
โ โโโ seed_sqlite.py # CLI: bootstrap SQLite from JSON mapping
โ โโโ vault_client.py # HashiCorp Vault KV-v2 client (pure requests)
โ โโโ env_setup.py # AI venv bootstrapper (user-venv isolation)
โ โโโ extension.py # %do / %fix / %review / %rework implementations
โ โโโ provider.py # LLM provider (Ollama / LiteLLM / MCP + rate limit)
โ โโโ metrics_agent.py # psutil agent โ runs on each worker EC2
โ โโโ monitoring_handler.py # Tornado WS + HTTP handlers for live dashboard
โ โโโ static/
โ โโโ monitoring.html # Dark-mode real-time monitoring dashboard
โ
โโโ scripts/
โ โโโ install_hub.sh # Automated Hub EC2 setup script
โ โโโ install_worker.sh # Automated Worker EC2 setup script
โ
โโโ AWS_EC2_DEPLOYMENT.md # End-to-end AWS deployment walkthrough
โโโ tests/ # 107-test mock-based unit suite
โโโ conftest.py # Module mock bootstrap (no real SSH/DB needed)
โโโ test_magics.py # Magic command unit tests
โโโ test_provider.py # LLM provider backend tests
โโโ test_spawner.py # SSH spawner lifecycle tests
โโโ test_spawner_lifecycle.py # RBAC, SessionStore & crash recovery tests
โโโ test_config.py # Hub config & security handler tests
๐ Security Model
| Control | How it works |
|---|---|
| OAuth Domain Lock | hosted_domain in hub_settings.json restricts login to one Google Workspace domain |
| Admin Isolation | admin_access = False โ admins cannot enter user containers via JupyterHub |
| 2-Tier RBAC | user.admin from JupyterHub panel; PermissionError raised before any cross-user action |
| Cross-User Blocking | BlockOtherUsersHandler returns 403 on /user/<other>/ path access attempts |
| OOM Enforcement | oom_score_adj = 500 written to /proc/self/oom_score_adj inside each kernel at startup |
| cgroups v2 Hard Limits | systemd-run --scope caps every kernel's memory and CPU at the OS level |
| Vault Secret Zero-Disk | Secrets fetched from Vault at spawn time, passed via environment โ never written to disk |
๐งช Running Tests
All 107 tests run with zero real infrastructure โ no SSH, no EC2, no Vault:
# Install dev dependencies
pip install -e ".[dev]"
# Run the full suite
pytest -v tests/
# Individual suites
pytest -v tests/test_magics.py # Magic commands (32 tests)
pytest -v tests/test_provider.py # LLM provider backends (18 tests)
pytest -v tests/test_spawner.py # SSH spawner lifecycle (11 tests)
pytest -v tests/test_spawner_lifecycle.py # RBAC + SQLite + crash recovery (43 tests)
pytest -v tests/test_config.py # Hub config & security (3 tests)
Expected:
107 passed, 1 warning in 2.90s
๐บ๏ธ Roadmap
| Task | Status | Description |
|---|---|---|
| Task 1 โ Admin Core & Lifecycle | โ Done | SQLite session state, 2-tier RBAC, start/stop/poll/clear_state |
| Task 2 โ cgroups v2 Isolation | โ Done | systemd-run hard MemoryMax + CPUQuota via pre_spawn_hook |
| Task 3 โ Vault Secret Injection | โ Done | KV-v2 client; zero-disk env injection at spawn; graceful degradation |
| Task 4 โ Live Monitoring Dashboard | โ Done | psutil agent โ WebSocket โ dark-mode real-time dashboard |
| Task 5 โ Controlled Env Strategy | โ Done | User venv at ~/.jupyterpilot/venv; [ai] optional package extra |
| Task 6 โ LLM & MCP Connectivity | โ Done | 3 backends (Ollama, LiteLLM, MCP) with retry, rate-limiting & Vault key reads |
| Task 7 โ Core Magics | โ Done | %do, %fix with cell context, traceback stripping, --run flag |
| Task 8 โ Agentic Magics | โ Done | %review (AST + LLM) and %rework --diff (unified diff preview) |
| Task 9 โ JIT User Provisioning | โ Done | Auto-creates OS users on Worker VMs on first login |
| Future โ Redis/Postgres State | ๐ Planned | Replace SQLite for multi-hub horizontal scaling |
| Future โ Auto-scaling Workers | ๐ Planned | Spin up new EC2 workers via AWS SDK when group capacity is low |
๐ค Contributing
- Fork the repository
- Create a branch:
git checkout -b feat/your-feature - Make changes and ensure tests pass:
pytest -v tests/ - Open a Pull Request with a clear description
# Development setup:
git clone https://github.com/wajoud/JupyterPilot.git
cd JupyterPilot
pip install -e ".[dev]"
pytest -v tests/ # all 107 should pass
๐ License
MIT License โ see LICENSE for details.
Built with ๐ง by @wajoud ยท Star โญ if this helps your team!
๐ ๏ธ Installation Guide ย ยทย ๐๏ธ Deploy to AWS EC2 ย ยทย ๐ค AI Magic Commands ย ยทย ๐ Live Monitoring
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 jupyterhub_pilot-0.2.0.tar.gz.
File metadata
- Download URL: jupyterhub_pilot-0.2.0.tar.gz
- Upload date:
- Size: 56.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
279a5558fa3eaaad863c35208375c4ef522e6a5d83d4179c7540b2f908675b80
|
|
| MD5 |
e5af288ae0a42594439362ce6722bd9e
|
|
| BLAKE2b-256 |
e5290df37f4eeb0b4d0c619e32dec0d0c5b1cbabee5b37766f3125a66a1fabe0
|
File details
Details for the file jupyterhub_pilot-0.2.0-py3-none-any.whl.
File metadata
- Download URL: jupyterhub_pilot-0.2.0-py3-none-any.whl
- Upload date:
- Size: 39.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
286989b41723492a14c393d0c2a16dd7dfc954ec25a4f29818e6f386c5463083
|
|
| MD5 |
5a3730fcc97f05d161884ae9251e6c4a
|
|
| BLAKE2b-256 |
84fa91f13328fe4a0d27cc52821dc0772da827bf766ea5111ea40dd1c23cc2d5
|