Metadata-Version: 2.4
Name: dv-platform
Version: 0.1.7
Summary: Git-like dataset version management platform: FastAPI backend + dv CLI, single pip install
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: fastapi>=0.115
Requires-Dist: uvicorn[standard]>=0.30
Requires-Dist: sqlalchemy>=2.0
Requires-Dist: pydantic>=2.7
Requires-Dist: pydantic-settings>=2.3
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: PyJWT>=2.8
Requires-Dist: python-dotenv>=1.0
Requires-Dist: boto3>=1.34
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13
Requires-Dist: httpx>=0.27
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: httpx>=0.27; extra == "test"

# 数据集版本管理平台（Dataset Version Platform）

> 一个“类 Git”的数据集版本管理平台：**内容寻址存储（CAS）+ 控制面/数据面分离 + 引用计数 GC**。
> 本仓库按照《数据集版本管理平台-技术路线.md》实现，覆盖里程碑 **M1 ~ M4**（并包含 M5 的 diff 等基础能力）。

## 核心特性（对照技术路线需求）

| # | 需求 | 实现 |
|---|------|------|
| 1 | 数据集版本管理与控制（类 Git） | Blob/Tree/Commit 三对象模型、提交历史、分支、标签、diff、append-only 回滚 |
| 2 | 不同版本存云端、可恢复 | 对象存储 + 不可变提交 + 任意版本检出（零拷贝） |
| 3 | 命令行上传 | `dv add/commit/push`，上传前按 sha256 去重（跳过已存在 blob） |
| 4 | 命令行快速下载 | `dv pull/fetch/clone/checkout`，多线程并行下载 + sha256 校验 |
| 5 | Web 展示数据集/版本/下载命令 | 数据集列表/详情、版本历史、文件清单/目录树、一键复制下载命令卡片 |
| 6 | Web 端删除数据集并同步清理云端存储 | 删除接口 + blob 引用计数 + 物理删除对象，共享文件不误删 |
| 7 | 数据集最小化保存在 Web 端 | 控制面只存元数据（哈希/清单/大小），文件内容全部在对象存储 |

## 总体架构

```
┌─────────────┐   ┌─────────────┐
│  Web UI     │   │  CLI (dv)   │
│  (React+AntD)│   │  (Typer)   │
└──────┬──────┘   └──────┬──────┘
       ▼                 ▼
┌──────────────────────────────────────┐
│ 控制面 Control Plane (FastAPI)       │
│  ├─ 认证/鉴权: JWT + API Token       │
│  ├─ 数据集/版本/分支/标签 CRUD        │
│  ├─ 版本引擎: commit/tree/blob       │
│  ├─ 预签名 URL 签发（上传/下载）      │
│  ├─ 存储清理: 引用计数 + 删除 GC      │
│  └─ 审计日志                          │
│  元数据库: PostgreSQL / SQLite(开发)  │
└───────────────┬──────────────────────┘
                ▼
┌──────────────────────────────────────┐
│ 数据面 Data Plane                    │
│  对象存储: 本地盘(开发) / MinIO / S3  │
│  objects/<sha256> 内容寻址，跨版本去重│
└──────────────────────────────────────┘
```

- **控制面只走元数据，数据面直连对象存储**：大文件不经过应用服务器。
- **Blob 引用计数**：同一 blob 被多个数据集引用时 `ref_count > 1`；删除数据集只对 `ref_count` 归零的 blob 做物理删除。

## 目录结构

```
backend/            FastAPI 后端（版本引擎、存储适配、RBAC、审计、删除 GC）
  app/
    engine.py         版本引擎：hash/tree/commit/ref_count/删除
    storage.py        存储适配：LocalStorage（本地盘）+ S3Storage（MinIO/OSS/COS/S3）
    routers/          auth / tokens / datasets / versions / storage / audit
  tests/             pytest 集成测试（7 个用例，覆盖核心流程）
cli/                dv 命令行（Typer + httpx + rich）
  dv/cli.py           全部子命令
  dv/transfer.py      并行上传/下载、sha256 扫描与校验
web/                React + TypeScript + Vite + AntD 前端
scripts/            demo_seed.py 演示数据；get_token.py 等
docker-compose.yml  Postgres + MinIO + backend
```

## 快速开始（开发环境）

### 1. 后端

```bash
python -m venv .venv
.venv\Scripts\pip install -r backend/requirements.txt      # Windows
# 或  source .venv/bin/pip install -r backend/requirements.txt   # Linux/macOS

cd backend
..\.venv\Scripts\python -m uvicorn app.main:app --port 8000
```

首次启动自动建库。访问：
- Web UI: http://localhost:8000
- API 文档: http://localhost:8000/docs
- 健康检查: http://localhost:8000/api/v1/health

默认管理员账号由 `/api/v1/auth/bootstrap` 创建（用户名 `admin`，密码来自 `DV_FIRST_ADMIN_PASSWORD`，默认 `admin123`）。
也可以 POST `/api/v1/auth/register` 注册新用户（第一个注册用户为 admin）。

### 2. 加载演示数据（可选）

```bash
.venv\Scripts\python scripts\demo_seed.py
```

### 3. CLI

```bash
.venv\Scripts\pip install -e ./cli        # 安装 dv 命令

dv login --token <API_TOKEN> --url http://localhost:8000
dv create default/my-dataset --visibility public
cd workdir
dv init default/my-dataset
dv add ./images ./labels.csv
dv status
dv commit -m "v1: 初始数据"
dv push                       # 上传新 blob（去重）+ 创建云端版本
dv log
dv tag v1.0
dv diff v1 HEAD
dv rollback v1 -m "回滚到 v1"
dv fetch default/my-dataset v1.0 -o ./data
dv clone default/my-dataset -o ./copy
dv ls --version v1.0
dv dataset delete default/my-dataset --yes
```

API Token 可在 Web 端或 `POST /api/v1/tokens` 创建（CLI 用长期 Token，Web 用 JWT）。

### 4. Web UI

```bash
cd web
npm install
npm run build        # 构建到 web/dist，后端自动挂载
# 开发模式（热更新，代理 /api 到 :8000）:
npm run dev          # http://localhost:5173
```

## API 一览（节选）

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | /api/v1/auth/login · register · bootstrap | 认证 |
| POST/GET/DELETE | /api/v1/tokens | API Token 管理（CLI） |
| POST | /api/v1/datasets | 创建数据集 |
| GET | /api/v1/datasets | 数据集列表（搜索/分页） |
| GET | /api/v1/datasets/{id} | 数据集详情 |
| **DELETE** | **/api/v1/datasets/{id}** | **删除数据集：元数据 + 引用计数归零对象的物理删除，返回释放字节** |
| GET | /api/v1/datasets/{id}/storage-info | 删除前影响范围（版本数/占用/共享 blob） |
| POST | /api/v1/datasets/{id}/presign/upload | 上传意向：哈希清单 → 仅返回缺失 blob 的预签名 URL（去重） |
| POST | /api/v1/datasets/{id}/commits | 创建版本（校验声明哈希均已存在） |
| GET | /api/v1/datasets/{id}/commits | 版本历史 |
| GET | /api/v1/datasets/{id}/versions/{ref}/manifest | 版本文件清单 |
| GET | /api/v1/datasets/{id}/versions/{ref}/presign/download | 版本下载预签名 URL 列表 |
| GET | /api/v1/datasets/{id}/versions/{ref}/download-command | 下载命令（Web 展示用） |
| GET | /api/v1/datasets/{id}/tree/{ref} | 目录树浏览 |
| GET | /api/v1/datasets/{id}/diff?base=&head= | 版本差异 |
| POST | /api/v1/datasets/{id}/rollback | 回滚（append-only 新提交） |
| POST/GET/DELETE | /api/v1/datasets/{id}/tags · branches | 标签 / 分支 |
| GET | /api/v1/datasets/{id}/stats | 存储统计 |
| GET | /api/v1/audit/logs | 审计日志（admin） |

`ref` 支持：tag 名、分支名、`HEAD`/`latest`、`vN` 序号、commit 哈希（前缀）。

## 存储后端切换

默认 `DV_STORAGE_BACKEND=local`（本地盘模拟对象存储，预签名 URL 为 HMAC 签名 API URL）。
切换到 S3 兼容对象存储（MinIO/OSS/COS/AWS S3）：

```bash
DV_STORAGE_BACKEND=s3 \
DV_S3_ENDPOINT=http://localhost:9000 \
DV_S3_BUCKET=datasets \
DV_S3_ACCESS_KEY=... \
DV_S3_SECRET_KEY=... \
.venv\Scripts\python -m uvicorn app.main:app --port 8000
```

CLI/Web 代码路径完全一致（都走预签名 URL 直传/直下）。

## Docker Compose（Postgres + MinIO + 后端）

```bash
# 先构建前端（把 web/dist 打进镜像）
cd web && npm install && npm run build && cd ..

docker compose up -d --build
# Web/API: http://localhost:8000    MinIO 控制台: http://localhost:9001
```

## 测试

```bash
.venv\Scripts\python -m pytest backend/tests -q
```

覆盖：认证/Token、数据集 CRUD、上传去重、提交/标签/diff/回滚、下载校验、
**RBAC（非 owner 删除返回 403）**、**跨数据集共享 blob 的引用计数与删除 GC**、物理对象释放。

## 里程碑对照

- ✅ M1 版本引擎 + CLI：三对象模型、add/commit/log/tag/rollback、本地盘对象存储、控制面只存元数据
- ✅ M2 云存储：S3 适配 + 预签名直传直下 + 去重 + 并发下载（分片/断点续传留作增强）
- ✅ M3 Web UI v1：列表/详情/版本历史/文件清单/目录树/下载命令卡片，元数据驱动渲染
- ✅ M4 平台化：删除 + 引用计数 + 存储同步清理、RBAC、Token、审计、存储统计
- 🔜 M5 进阶：分片断点续传、Merkle 分层树、软删除回收站、Webhooks、SDK

## 关键设计说明

- **最小化存储**：数据库只存哈希/清单/大小，文件内容永不入库；相同内容全平台只存一份。
- **删除原子性**：元数据删除与引用计数更新在同一事务；物理删除失败可由 `gc_jobs` 兜底重试。
- **完整性**：上传 PUT 时校验 `sha256(body)==key`；下载后校验 sha256。
- **安全**：私有桶 + 短时效预签名 URL（默认 15 分钟）+ 数据集级 RBAC + 全量审计。
## 前端界面（ModelScope 风格）

Web 界面参考 ModelScope 社区风格设计：

- 顶部品牌导航 + 「新建数据集」入口 + 用户菜单
- 首页：渐变 Hero 搜索区 + 数据集卡片网格（彩色图标、描述、版本/文件/大小标签、可见性徽标、更新时间）
- 详情页：头部大图标 + 统计条（版本/文件/大小/最新版本/更新时间）+ **复制下载命令** 卡片 + 删除入口（owner）
- 标签页：数据集介绍（自动渲染 README.md）、文件（左侧目录树 + 文件表 + 单文件下载）、版本历史、标签、分支、版本差异
- 登录页：渐变品牌背景 + 居中卡片

构建：`cd web && npm run build`（产物 `web/dist`，由后端自动托管）。
UI 冒烟测试：`scripts/ui_check.py`、`scripts/ui_check2.py`（Playwright，自动登录并检查列表/详情/文件/版本/diff/删除弹窗）。
页面截图：`scripts/ui_check.py` 运行后输出到 `.shots/`。

## 下载进度

- **CLI**：`dv push / pull / fetch / clone / checkout` 均显示 rich 进度条——字节进度、文件数、当前文件、传输速度与剩余时间；下载按流式分块实时刷新，且断点续传（已存在且哈希匹配的文件自动跳过）。可用 `--no-progress` 关闭。
- **Web 详情页**：新增「下载全部（ZIP）」按钮，弹窗实时显示百分比、当前文件与已下载文件数，完成后打包为 `<数据集名>.zip` 下载。

## 账号注册 与 Token 管理

- **注册**：登录页有「立即注册」入口（用户名/邮箱/密码/确认密码），第一个注册用户自动成为管理员；注册成功即自动登录。
- **Web Token 管理**（导航栏「Token 管理」）：创建 Token（名称 + 过期时间：永不过期/7/30/90/365 天）、列表展示过期时间与最近使用、吊销；明文仅在创建弹窗显示一次，可一键复制。
- **CLI**：`dv token create --name X [--expires 2027-01-01T00:00:00]`、`dv token list`、`dv token revoke <id>`。
- 所有 API 响应均带 `Cache-Control: no-store`，前端请求亦禁用缓存，保证列表变更即时可见。

## README 展示（ModelScope 风格）

- 每个数据集详情页**默认展示 README.md**（「数据集介绍」标签页），风格对齐 ModelScope：
  - GitHub 风格 Markdown 渲染：标题锚点链接、表格、代码块语法高亮（highlight.js）、引用块、任务列表、分隔线
  - 相对路径图片自动解析为数据集内文件的预签名 URL（支持懒加载）
  - 外部链接新窗口打开
  - 支持 README 中的原生 HTML（rehype-raw）
- 头部有 `README.md` 标识与「下载 README.md」按钮
- 无 README 时展示引导空态（含 `dv add README.md && dv commit ... && dv push` 示例）

## 打包安装为 Python 库（推荐部署方式）

仓库根目录的 `pyproject.toml` 把 **后端（`backend/app`）** 与 **CLI（`cli/dv`）** 打包成
**一个发行版 `dv-platform`**，`pip install` 后即可得到两个命令：

| 命令 | 作用 |
|------|------|
| `dv` | 数据集版本管理 CLI（不变） |
| `dv-server` | 启动/管理后端服务（前台或后台守护进程） |

```bash
# 在仓库根目录（含 pyproject.toml）
pip install .            # 或开发模式: pip install -e .
pip install ".[test]"    # 可选：测试依赖
```

> 如果环境里以前用 `pip install -e ./cli` 装过旧版 `dv-cli`，先执行 `pip uninstall dv-cli`，
> 避免与新版 `dv-platform` 里的 `dv` 包/`dv` 命令冲突。

### 快速开始

```bash
# 1) 后台启动服务（默认数据目录 ~/.dvserver，监听 0.0.0.0:8000）
dv-server start
dv-server status

# 自定义端口 / 数据目录 / 前台运行
dv-server start --port 9000 --data-dir D:\dvdata
dv-server run --host 127.0.0.1 --port 8000        # 前台（Ctrl+C 停止）
dv-server stop --port 9000
dv-server restart
dv-server logs --tail 50
```

需要开机自启时，可把 `dv-server start` 加入 Windows 任务计划程序（开机触发）或 systemd 单元。

```bash
# 2) 服务器端引导管理员并签发 API token（无需 Web / 已有 token）
dv-server bootstrap --password <初始密码>   # 首次创建 admin
dv-server token create --name ci            # 输出明文 token（仅显示一次）

# 3) CLI 使用
dv login --token <上一步输出的TOKEN> --url http://127.0.0.1:8000
dv create team/my-dataset
dv clone team/my-dataset -o ./data
```

### 首次启动引导（setup 向导，OpenClaw 风格）

在**全新数据目录**执行 `dv-server start` 且处于交互终端时，会自动弹出首次配置引导，
逐项询问并写入配置、初始化数据库与管理账号（只跑一次；已有 `.env` 后即正常启动）：

```text
dv-server setup                      # 手动进入引导（可随时执行）
dv-server setup --force              # 忽略已有 .env 重新引导
dv-server start --non-interactive    # 跳过引导，用默认配置启动（适合脚本/CI/管道）
```

引导流程只让你选择 **一个安装目录**（.env/数据库/日志/数据集大文件都在其中），随后是监听地址/端口、存储后端（local/s3）、对外访问地址、
元数据库（sqlite / postgres）、管理员账号密码、是否立即签发初始 API token。
完成后自动生成 `.env`、建库、创建管理员并（可选）打印初始 token，随后服务照常启动。

### 单目录 / 免交互初始化

默认 **单目录模式**：配置文件、元数据库、日志与数据集大文件全部放在同一个目录，只记一个路径：

```bash
# 交互引导（选 single，默认）
dv-server setup

# 一条命令初始化到指定单目录（免交互，适合脚本/CI）
dv-server setup --root D:/dv --non-interactive --admin-pass 'secret' --token-name ci
# → D:/dv 下自动生成 .env / dv.db / storage/objects，管理员 admin，并签发初始 token
dv-server start --data-dir D:/dv
```

想自定义（例如把大文件单独放其它磁盘）：初始化完成后执行 `dv-server config set DV_STORAGE_ROOT=D:/bigdisk` 再 `dv-server restart`。

### 查看帮助（help）

所有命令都支持 `--help`：

```bash
dv --help                 # CLI 全部命令：login/create/init/add/commit/push/pull/...
dv token --help           # token create/list/revoke（客户端方式，需先登录）
dv dataset --help         # 数据集管理子命令
dv-server --help          # run/start/stop/status/restart/logs/setup/bootstrap/token
dv-server run --help      # --host/--port/--workers/--data-dir/--storage-root
dv-server token --help    # create/list/revoke（服务器端方式）
dv-server setup --help     # 首次配置引导参数
dv-server config --help  # show / set / unset（查看/修改 .env 配置）
```

### 服务器端新增 / 管理 API token（免 Web）

在服务器机器上直接操作数据目录里的数据库，**不需要 Web UI，也不需要已有 token**：

```bash
dv-server bootstrap                            # 首次创建 admin（已有用户会报错）
dv-server bootstrap --password <初始密码>      # 或指定初始密码

dv-server token create --name ci --user admin  # 签发 token，明文只打印一次
dv-server token list                           # 列出全部 token
dv-server token revoke <id>                    # 吊销 token
```

与客户端方式的区别：`dv token create` 需要先 `dv login`（已有凭据）；`dv-server token ...`
直接在服务器本地数据库签发，适合初始化部署、CI 脚本、管理员应急（Web UI 的 Tokens 页等价）。

### 查看 / 修改服务器配置（`dv-server config`）

不用手改 `.env`，直接看/改服务器配置：

```bash
dv-server config show                  # 查看全部配置（密钥自动打码）+ 生效路径
dv-server config show --json           # JSON 输出（脚本用）
dv-server config show --show-secrets   # 明文显示密钥（如需要复制时）

dv-server config set DV_STORAGE_ROOT=D:/dataset-data DV_PUBLIC_BASE_URL=http://10.0.0.5:8000
dv-server config unset DV_WEB_DIST
```

说明：
- 只接受平台认识的 `DV_*` 键（未知键会报错并列出可用键）；
- 修改后需 `dv-server restart` 生效（配置在进程启动时读取）；
- 监听地址/端口是启动参数（`--host/--port`），不写在 `.env` 里。
### 指定数据集存储路径

数据集**文件内容**存放在对象存储（本地后端 = 磁盘目录），默认是**数据目录下的 `storage/`**，
可用以下任一方式改到任意位置：

```bash
# 方式 1：启动参数（run / start / restart 均支持，推荐）
dv-server start --storage-root D:/dataset-objects

# 方式 2：环境变量
DV_STORAGE_ROOT=D:/dataset-objects dv-server start

# 方式 3：编辑数据目录里的 .env（DV_STORAGE_ROOT=...）
```

说明：
- 元数据库与对象目录分开：元数据库由 `DV_DATABASE_URL` 决定（默认 `sqlite:///<data dir>/dv.db`，
  生产建议 PostgreSQL）；对象目录由 `DV_STORAGE_ROOT` 决定；
- `--storage-root` 相对路径以**数据目录**为基准；绝对路径则完全独立于数据目录，方便放独立磁盘/挂载点；
- S3 后端（`DV_STORAGE_BACKEND=s3`）对象不落本地，此选项可忽略；
- 存储路径是**服务器端配置**，客户端不感知——CLI/Web 仍走预签名 URL 直传直下。

### `dv-server` 数据目录（data dir）

所有可变数据（SQLite `dv.db`、本地对象存储 `./storage`、`.env`、PID、日志）都落在
**数据目录**内，因此服务可放在任意位置、完全自包含：

- `run`（前台）：默认使用当前目录；
- `start/stop/status/restart/logs/bootstrap/token`：默认 `~/.dvserver`（可用 `DV_SERVER_DIR` 或 `--data-dir` 覆盖）。

首次 `start` 会自动生成 `.env`（含随机 `DV_JWT_SECRET`，重启后凭据保持有效）。
生产建议：`DV_DATABASE_URL` 换 PostgreSQL、`DV_STORAGE_BACKEND=s3` 指向 MinIO/OSS/S3，
并把 `DV_PUBLIC_BASE_URL` 设为客户端可达的地址（预签名 URL 以此为基准）。

### 发布到索引后即可 `pip install dv-platform` 包名直装

`pip install .` 只对本地生效。要让任意机器 `pip install dv-platform` 直接安装，需把构建产物
发布到 pip 能访问的索引：

```bash
# 1) 构建 sdist + wheel（产物在 dist/）
pip install build twine
python -m build

# 2a) 发布到官方 PyPI（代码将公开；内部代码请用 2b）
#     先在 https://pypi.org 注册并创建 API token，然后：
python -m twine upload dist/*

# 2b) 或发布到公司私有源（devpi / Nexus / Artifactory / GitLab·Gitea Registry）
python -m twine upload --repository-url https://pypi.example.com/ dist/*

# 3) 任何机器安装（私有源示例）
pip install dv-platform --index-url https://pypi.example.com/simple \
    --extra-index-url https://pypi.tuna.tsinghua.edu.cn/simple
```

- 不想搭源：内网起一个静态 PEP 503 目录（`simple/dv-platform/` 下放 wheel + index.html），
  或 `pip install dv_platform-0.1.1-py3-none-any.whl` 直接装 wheel；
- 有 Git 仓库也可 `pip install "dv-platform @ git+https://.../dv-platform.git"` 直装；
- 发版规则：改代码后递增 `pyproject.toml` 的 `version`（与 `dv --version` 一致），
  再 `python -m build` + `twine upload dist/*`。
### 多客户端并发上传/下载

- 上传/下载走预签名 URL（S3 后端直连对象存储，完全不经过应用进程）；
- 本地后端（`DV_STORAGE_BACKEND=local`）的 PUT/GET 已改为**流式收发**：上传边收边写临时文件
  并校验 sha256 后原子改名，下载用 `FileResponse` 流式返回，多客户端并发传大文件不会把文件整体载入内存；
- SQLite（开发默认）开启 WAL + `busy_timeout=30000`，且每个事务以 `BEGIN IMMEDIATE` 开始，
  多个客户端同时上传相同 blob（去重竞态）或同时向同一数据集 push 时不会出现
  “database is locked”/重复版本号/丢更新；
- 同一台机器多客户端并行传输建议保持 `--workers 1`；要横向扩容请换 PostgreSQL + S3 再加大 workers。
