Metadata-Version: 2.4
Name: yao-api
Version: 0.1.0
Summary: Python 版 Knife4j：自研前端的 API 文档服务层（挂载 + 文档密码 + 多服务聚合）
Author: yao-api authors
License-Expression: MIT
Keywords: openapi,swagger,api-docs,knife4j,fastapi,documentation
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi>=0.100
Requires-Dist: starlette>=0.27
Requires-Dist: pydantic>=2
Requires-Dist: httpx>=0.24
Requires-Dist: uvicorn>=0.20
Provides-Extra: docx
Requires-Dist: python-docx; extra == "docx"
Provides-Extra: flask
Requires-Dist: flask; extra == "flask"
Provides-Extra: django
Requires-Dist: django; extra == "django"
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: httpx; extra == "test"
Provides-Extra: all
Requires-Dist: python-docx; extra == "all"
Requires-Dist: flask; extra == "all"
Requires-Dist: django; extra == "all"
Requires-Dist: uvicorn; extra == "all"
Requires-Dist: pytest; extra == "all"
Dynamic: license-file

# yao-api · Python 版 Knife4j

把 Knife4j 那套「本土化、开箱即用」的 API 文档体验搬到 Python 生态（FastAPI 优先）。
**前端完全自研（Vue 3 + Element Plus）**，后端只负责挂载构建产物、文档密码鉴权、可选的多服务聚合。

## 特性

- 一行接入：FastAPI / Flask / Django 应用零改造业务代码即获本土化文档
- 挂载自研前端 SPA 到任意子路径（默认 `/docs/yao-api`）
- 登录鉴权（可选）：`doc_username` 与 `doc_password` **成对设置**才开启；启用后前端先展示登录页，凭证正确才加载接口数据。二者任一为空则文档完全公开（无默认用户名）
- 多服务聚合：合并多个 OpenAPI 到统一入口
- 自定义 Markdown 文档页、全局参数、接口搜索、离线导出（Markdown/HTML/Word）

## 安装

```bash
pip install yao-api
# 或本地开发（含可选依赖：python-docx / flask / django / uvicorn）
pip install -e .[all]
```

前端构建产物会随包一起打进 wheel（位于 `yao_api/ui/dist`），因此 `pip install yao-api` 后**无需单独构建前端**即可使用：默认即挂载内置 SPA。仅当你要自定义前端时才需要自行 `vite build` 并传 `frontend_dist`。

```bash
cd frontend
npm run build:docs   # 仅在自定义前端时才需要（base 已固为 /docs/yao-api/）
```

## 用法（FastAPI）

```python
from fastapi import FastAPI
from yao_api import YaoAPI

app = FastAPI(title="订单服务")

YaoAPI(
    app,
    mount_path="/docs/yao-api",
    title="订单服务 API",
    description="订单、支付、用户相关接口文档",
    version="1.0.0",
    group_order=["订单", "支付", "用户"],
    doc_username="admin",    # 可选：与 doc_password 成对设置才开启登录
    doc_password="yao123",   # 可选：与 doc_username 成对设置才开启登录
    aggregation=[{"name": "用户服务", "url": "http://user-svc/openapi.json"}],
)
```

启动后访问 `http://localhost:8000/docs/yao-api`：若成对设置了 `doc_username` 与 `doc_password`，会先进入登录页，输入正确凭证后才加载接口数据；二者任一为空则文档直接公开（无默认用户名）。

## 用法（Flask）

```python
from flask import Flask
from yao_api import YaoFlask

app = Flask(__name__)
# 不传 frontend_dist 即使用包内置 SPA（pip install 后无需自行 build 前端）
YaoFlask(app, spec=my_openapi_dict, doc_username="admin", doc_password="yao123")
```

## 用法（Django）

```python
# urls.py
from yao_api import YaoDjango
# 不传 frontend_dist 即使用包内置 SPA
yao = YaoDjango(spec=my_openapi_dict,
                 doc_username="admin", doc_password="yao123")
urlpatterns = [path("", include(yao.urlpatterns))]
```

## 命令行一键起服务

不想改业务代码、只想给已有的 ASGI 应用挂个文档？用 `yao-api-serve`（安装后自带）直接拉起：

```bash
# 给任意 FastAPI/Starlette 应用挂文档（--app 为 'module:attr'）
yao-api-serve --app myapp:app --port 8000

# 开启文档密码、自定义挂载路径与分组顺序
yao-api-serve --app myapp:app --mount-path /docs --doc-password secret \
          --group-order 订单,支付,用户 --title "我的 API"

# 指定自定义前端构建产物
yao-api-serve --app myapp:app --dist ./frontend/dist
```

参数说明：

| 参数 | 默认值 | 说明 |
| --- | --- | --- |
| `--app` | 必填 | 应用导入字符串，如 `myapp:app` |
| `--host` | `0.0.0.0` | 监听地址 |
| `--port` | `8000` | 监听端口 |
| `--mount-path` | `/docs/yao-api` | 文档挂载路径 |
| `--openapi-url` | `/openapi.json` | 应用原生 OpenAPI 地址 |
| `--title` | 取应用 title | 文档标题 |
| `--description` | 取应用 description | 文档简介，主页显示 |
| `--version` | 取应用 version | API 版本号，主页显示 |
| `--doc-password` | 关闭 | 文档登录密码；需与 `--doc-username` 同时设置才开启登录 |
| `--doc-username` | 关闭 | 文档登录用户名；需与 `--doc-password` 同时设置才开启登录 |
| `--group-order` | 无 | 逗号分隔的分组顺序 |
| `--dist` | 内置 `ui/dist` | 自定义前端构建产物目录 |
| `--reload` | 关 | 热重载（开发用） |

本地开发期也可作为模块运行（等价于 console script）：`python -m yao_api.cli --app myapp:app`。

## 开发 / 测试

```bash
# 1) 安装含测试/适配器依赖
pip install -e ".[test,flask,django]"

# 2) 跑测试（任选其一）
pytest                                   # 推荐，跑全部
python tests/smoke.py                     # 冒烟：挂载/SPA/鉴权/聚合
python tests/test_adapters.py            # Flask/Django 适配器（缺依赖则跳过）
python tests/test_units.py               # 核心模块单测（auth/spec/aggregate/config/安全）

# 3) 本地前端联调（可选，仅自定义前端时需要）
cd frontend && npm install && npm run dev
```

- CI：`.github/workflows/ci.yml` 串联 `ruff` 静态检查 → 多 Python 版本（3.9~3.13）`pytest` → `build` + `twine check`。
- 仓库自带 `.venv`（managed python 3.13），离线环境下通过 `.venv/Lib/site-packages/yao-api-local.pth` 指向本地 `.deps` 与项目根，可直接 `python tests/*.py` 验证。

## 部署

### Docker（推荐）

```bash
# 构建并运行官方示例（文档挂在 /docs/yao-api）
docker build -t yao-api .
docker run -p 8000:8000 yao-api

# 生产：换成你自己的应用
docker run -p 8000:8000 yao-api \
  yao-api-serve --app your_pkg.your_module:app --host 0.0.0.0 --port 8000
```

### docker-compose（聚合演示）

`docker-compose.yml` 编排了 `docs`（挂示例 app + 聚合）与 `user-svc`（被聚合的第二个服务）：

```bash
docker compose up --build
# 访问 http://localhost:8000/docs/yao-api
```

### 反向代理（必须前置 HTTPS）

文档密码是 HTTP Basic，**生产环境务必在前面用 nginx 等终止 TLS**，并加安全头：

```nginx
server {
    listen 443 ssl;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

进程管理可用 `gunicorn -k uvicorn.workers.UvicornWorker -w 4 your_app:app`，或用 `yao-api-serve` 配合 systemd/supervisor。

## 安全最佳实践

- **成对鉴权**：`doc_username` 与 `doc_password` 必须**同时设置**才开启登录；任一为空文档完全公开，且无默认用户名（避免弱默认凭据）。
- **必须 HTTPS**：Basic 凭证明文传输，任何暴露公网的服务都要前置 TLS（见部署），否则密码可被中间人截获。
- **安全响应头**：SPA 与数据接口自动附带 `Content-Security-Policy` / `X-Content-Type-Options` / `Referrer-Policy` / `Permissions-Policy`，降低 XSS、嗅探、referrer 泄漏风险。
- **失败尝试限速（防爆破）**：默认对同一客户端 IP 的失败登录计数，窗口 60s 内超过 50 次返回 `429`。多实例部署请改用 Redis 等共享存储（替换 `AuthRateLimiter._failures`），否则各实例独立计数、效果会被水平扩展稀释。可通过 `auth_rate_limit=False` 关闭。
- **密码不下发**：前端只拿到 `auth_enabled` 布尔，明文密码绝不出现在 `config.json`；校验在服务端用恒定时间比较，防时序侧信道。
- **无原生弹窗**：401 不带 `WWW-Authenticate`，由自研前端渲染登录页，避免浏览器原生登录框遮挡。

## 发布到 PyPI

两种方式，任选其一：

1. **Trusted Publisher（OIDC，推荐，免 Token）**：在 PyPI 项目 `yao-api` 的「Publishing」里配置 GitHub Actions 信任发布（指定仓库、工作流 `ci.yml`、环境）。CI 的 `build` job 已产出 `dist/*` 产物，后续把上传步骤指向该产物即可，无需任何 Token。
2. **本地上传**：在有网机器执行
   ```bash
   pip install build twine
   python -m build
   twine upload dist/*
   ```
   需要你的 PyPI 账号 Token（`__token__`）。

> 发版前请同步 `yao_api/__init__.py` 的 `__version__` 与 `pyproject.toml` 的 `version`。

## 目录结构

```
yao-api/
├── yao_api/            # Python 服务层（auth / spec / config / aggregate / core / 适配器）
├── frontend/           # 自研前端源码（Vue3）
├── example/            # 演示 FastAPI 应用
└── tests/              # 冒烟测试
```
