Manual-commit notebook snapshot extension (JupyterLab 4):
Backend
- Storage adapter layer (Local + S3 via boto3, pip extra)
- Dumb put/get/list/delete contract; key validation
- S3Storage: lazy boto3 import, S3ConnectionError with friendly
messages + connectivity check at startup
- SnapshotStore
- Strip code outputs/execution_count, MD5 on cleaned JSON
- Gzipped envelope (id/timestamp/name/description/hash/size/notebook)
- Append-only manifest.json (rebuildable from version files)
- SnapshotUnchangedError when new hash matches most recent version
- REST API (commit / list / content) under /snapshot/ namespace
- traitlets config (storage_type, local_root, s3_* + S3_* env fallback)
Frontend
- snapshot:commit command (toolbar button + command palette)
- Dialog for name + description
- SnapshotPanel (left sidebar timeline) + DiffWidget (main area)
- Cell diff: id-match first, LCS fallback; jsdiff for line diff
- Restore via model.fromJSON() + context.save() (no refresh)
- All AGENTS.md hard conventions enforced
Tests: 33 backend pytest passing (storage + store + routes)
Docs: AGENTS.md, design.md, README.md synced with implementation.
Co-Authored-By: Claude <noreply@anthropic.com>
148 lines
4.9 KiB
Markdown
148 lines
4.9 KiB
Markdown
# notebook-snapshot
|
||
|
||
JupyterLab 扩展:**手动保存 .ipynb 快照**,无 Git 依赖。
|
||
|
||
左侧栏查看版本时间轴、Cell 级 diff、一键无刷新恢复。
|
||
|
||
## 功能
|
||
|
||
- 工具栏「保存快照」按钮 / 命令面板 `snapshot:commit` 触发
|
||
- 对话框填写 `name`(必填)+ `description`(可选)后保存
|
||
- 内容与最近版本相同时自动跳过(弹 warning),避免重复快照
|
||
- 左侧栏时间轴,支持对比(Cell 级 diff,行级红绿高亮)和恢复
|
||
- 恢复走 `model.fromJSON()` + `context.save()`,无页面刷新
|
||
- 历史 append-only,永不删除 / 改写
|
||
|
||
## 存储后端
|
||
|
||
- **Local**(默认):`<root_dir>/.notebook_versions` 或自定义路径
|
||
- **S3**(pip extra):通过 boto3 对接 AWS S3 / MinIO / 任何 S3-API 兼容服务
|
||
|
||
## 环境要求
|
||
|
||
- JupyterLab >= 4.0.0
|
||
- Python >= 3.10
|
||
- Node.js(构建前端用)
|
||
|
||
## 安装
|
||
|
||
```bash
|
||
# 1. 后端(基础包零依赖)
|
||
pip install snapshot
|
||
|
||
# 2. 前端构建
|
||
jlpm install
|
||
jlpm build
|
||
|
||
# 3. 启用 server extension
|
||
jupyter server extension enable snapshot
|
||
jupyter labextension develop . --overwrite # dev 安装;正式版 wheel 跳过此步
|
||
```
|
||
|
||
可选 S3 后端:
|
||
|
||
```bash
|
||
pip install "snapshot[s3]" # 装 boto3
|
||
```
|
||
|
||
## 配置
|
||
|
||
写在 `~/.jupyter/jupyter_server_config.py`(用户级)或 `<项目>/jupyter_server_config.py`(项目级):
|
||
|
||
| 字段 | 默认 | 说明 |
|
||
|------|------|------|
|
||
| `storage_type` | `"local"` | `"local"` 或 `"s3"` |
|
||
| `local_root` | `""` → `<root_dir>/.notebook_versions` | Local 存储根目录;**建议显式设置**避免污染 notebook 目录 |
|
||
| `s3_endpoint` | `""`(env: `S3_ENDPOINT`) | `localhost:9000`(MinIO)/ `s3.us-east-1.amazonaws.com`(AWS) |
|
||
| `s3_access_key` | `""`(env: `S3_ACCESS_KEY`) | |
|
||
| `s3_secret_key` | `""`(env: `S3_SECRET_KEY`) | |
|
||
| `s3_bucket` | `"notebook-snapshot"` | 不存在自动建 |
|
||
| `s3_secure` | `False` | HTTPS |
|
||
| `s3_prefix` | `""` | 桶内 key 前缀 |
|
||
|
||
### 示例
|
||
|
||
```python
|
||
# 切到自定义 local 目录
|
||
c.SnapshotConfig.local_root = "/var/lib/jupyter/snapshots"
|
||
|
||
# 切到 MinIO
|
||
c.SnapshotConfig.storage_type = "s3"
|
||
c.SnapshotConfig.s3_endpoint = "minio.example.com:9000"
|
||
c.SnapshotConfig.s3_bucket = "team-snapshots"
|
||
# 凭证走 env:export S3_ACCESS_KEY=... ; export S3_SECRET_KEY=...
|
||
|
||
# 切到 AWS S3
|
||
c.SnapshotConfig.storage_type = "s3"
|
||
c.SnapshotConfig.s3_endpoint = "s3.us-east-1.amazonaws.com"
|
||
c.SnapshotConfig.s3_secure = True
|
||
# 用 AWS 标准凭证:export AWS_ACCESS_KEY_ID=... ; export AWS_SECRET_ACCESS_KEY=...
|
||
# (注:当前版本仅读取 S3_* 环境变量;如需 AWS_* 自动识别,参考 boto3 文档配置)
|
||
```
|
||
|
||
环境变量 / 命令行 / 配置文件三种方式优先级遵循 traitlets 规则;改完配置**必须重启 `jupyter lab`**。
|
||
|
||
## 开发
|
||
|
||
```bash
|
||
source .venv/bin/activate # 本仓库用 uv/venv
|
||
pip install -e ".[dev,test,s3]" # 后端装 dev/test/s3 依赖
|
||
.venv/bin/jlpm install # 前端依赖
|
||
.venv/bin/jlpm build # 一次性构建(tsc + rspack + labextension)
|
||
.venv/bin/jlpm watch # 终端 1:监听 src/ 自动重编
|
||
jupyter lab # 终端 2:启动 JupyterLab
|
||
```
|
||
|
||
**改了 Python 后端**:重启 `jupyter lab`(不需要 rebuild 前端)。
|
||
**改了前端 TypeScript**:`jlpm watch` 自动重编,浏览器刷新即可。
|
||
|
||
## 验证
|
||
|
||
```bash
|
||
# 后端
|
||
.venv/bin/pytest snapshot/tests/ -vv
|
||
|
||
# 前端类型检查 + 打包
|
||
.venv/bin/jlpm build
|
||
|
||
# Lint
|
||
.venv/bin/jlpm lint:check
|
||
```
|
||
|
||
## 架构
|
||
|
||
参见 [design.md](design.md)。
|
||
|
||
简要:
|
||
- **后端**:`/snapshot/` 命名空间下三个端点(commit / list / content);`SnapshotStore` 包装 `StorageAdapter`(Local + S3),gzip + envelope 存盘,manifest 是可重建的索引
|
||
- **前端**:`SnapshotModel` + `SnapshotPanel`(左侧栏) + `DiffWidget`(主区) + `snapshot:commit` 命令(工具栏 + 命令面板)
|
||
|
||
硬约定(实现时不得违背)见 [AGENTS.md](AGENTS.md)。
|
||
|
||
## 排错
|
||
|
||
```bash
|
||
jupyter server extension list # 后端 extension 状态
|
||
jupyter labextension list # 前端 extension 状态
|
||
```
|
||
|
||
- **工具栏没有「保存快照」按钮**:刷新浏览器;确认 `jupyter labextension list` 显示 enabled + OK
|
||
- **保存时 400**:检查 `path` / `content` / `name` 是否齐全
|
||
- **保存后弹"内容未变更"**:正常行为——内容与最近版本 hash 相同会自动跳过,改动 cell 后再保存即可
|
||
- **S3 连不上**:确认 `endpoint`(不带 scheme)、`secure`(http/https)、bucket 存在性;凭证走 env 别写进配置
|
||
|
||
## 卸载
|
||
|
||
```bash
|
||
jupyter server extension disable snapshot
|
||
pip uninstall snapshot
|
||
```
|
||
|
||
## AI 工具支持
|
||
|
||
本项目遵循 [AGENTS.md](https://agents.md) 标准,配套符号链接:
|
||
- `CLAUDE.md` → `AGENTS.md`(Claude Code)
|
||
- 其他工具:Cursor / Copilot / Windsurf / Aider 等直接读取 `AGENTS.md`
|
||
|
||
详细开发规范见 [AGENTS.md](AGENTS.md),设计文档见 [design.md](design.md)。
|