Metadata-Version: 2.5
Name: eeo-py-sdk
Version: 0.2.0
Summary: ClassIn (EEO) API Python SDK — full coverage of v1/v2 endpoints
Project-URL: Homepage, https://github.com/EeoApiSupport/classin-sdk-py
Project-URL: Repository, https://github.com/EeoApiSupport/classin-sdk-py
Project-URL: Issues, https://github.com/EeoApiSupport/classin-sdk-py/issues
Project-URL: Documentation, https://docs.eeo.cn/api/
Author-email: eeoapisupport <apisupport@eeo.com>
License: MIT
License-File: LICENSE
Keywords: api,classin,eeo,online-education,sdk
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Education
Requires-Python: >=3.10
Requires-Dist: requests>=2.28.0
Description-Content-Type: text/markdown

# eeo-py-sdk

[![Python](https://img.shields.io/badge/python-%3E%3D3.10-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Status](https://img.shields.io/badge/status-beta-orange.svg)]()
[![GitHub](https://img.shields.io/badge/GitHub-EeoApiSupport%2Fclassin--sdk--py-black?logo=github)](https://github.com/EeoApiSupport/classin-sdk-py)

ClassIn (EEO) API Python SDK —— 完整覆盖 ClassIn API v1 / v2 共 **68 个业务方法**。

📖 官方文档：<https://docs.eeo.cn/api/>

---

## 特性

- ✅ 覆盖 v1 partner / cloud + v2 LMS / school 全部业务接口
- ✅ 自动计算 v1 `safeKey` 和 v2 `X-EEO-SIGN`，调用方无需关心签名
- ✅ 仅对"未建立的连接"做安全重试（指数退避），永远不会重复落库 / 重复扣费
- ✅ `requests.Session` 复用连接，支持 `with` 上下文管理器
- ✅ 单次请求 / 上传请求超时可分别配置，支持 `(connect, read)` 二元组
- ✅ Python ≥ 3.10，全量类型注解

## 安装

```bash
pip install eeo-py-sdk
```

或从源码安装：

```bash
git clone https://github.com/EeoApiSupport/classin-sdk-py.git
cd classin-sdk-py
pip install -e .
```

使用 [uv](https://github.com/astral-sh/uv)：

```bash
uv pip install eeo-py-sdk
```

## 快速开始

```python
from eeo import ClassInAPI

api = ClassInAPI(school_uid=123456, school_secret="your-secret")

# 注册用户
resp = api.register("13800138000", "mypassword", nickname="张三")

# 创建课程
resp = api.add_course("高一数学班", mainTeacherUid=1001001)

# 创建课节
resp = api.add_course_class(
    courseId=469383, className="第一课", teacherUid=1001001,
    beginTime=1716825600, endTime=1716829200,
    seatNum=6, record=1, live=1, replay=1,
)

# 添加学生
resp = api.add_course_student(courseId=469383, studentUid=1002001)

# 获取激活客户端链接
resp = api.get_login_linked(uid=1002001, courseId=469383, classId=23634)
```

> 推荐通过环境变量加载凭据，避免泄漏。仓库根目录 [main.py](main.py) 提供了示例。

## 客户端参数

```python
ClassInAPI(
    school_uid,                                # 学校 SID
    school_secret,                             # 学校密钥
    domain="https://api.eeo.cn",               # 自定义域名（海外节点替换即可）
    max_retries=3,                             # 0 = 关闭重试
    backoff_factor=0.5,                        # 指数退避：0.5s / 1s / 2s
    timeout=30,                                # 普通请求超时，可传 (connect, read)
    upload_timeout=60,                         # 文件上传超时，可传 (connect, read)
)
```

## 接口覆盖（68 个方法）

<details>
<summary>展开查看完整接口列表</summary>

### 用户（3）
| 方法 | 说明 | 版本 |
|------|------|------|
| `register` | 注册用户（手机号或邮箱） | v1 |
| `register_multiple` | 批量注册用户 | v1 |
| `modify_password` | 修改用户密码 | v1 |

### 学校教师 / 学生（7）
| 方法 | 说明 | 版本 |
|------|------|------|
| `add_teacher` | 添加学校老师 | v1 |
| `edit_teacher` | 编辑老师信息 | v1 |
| `stop_using_teacher` | 停用老师 | v1 |
| `restart_using_teacher` | 启用老师 | v1 |
| `add_school_student` | 添加学校学生 | v1 |
| `edit_school_student` | 编辑学校学生信息 | v1 |
| `modify_course_student_nickname` | 用学生姓名同步课程下班级昵称 | v1 |

### 学校标签（3）
| 方法 | 说明 | 版本 |
|------|------|------|
| `add_school_label` | 创建机构标签 | v1 |
| `update_school_label` | 修改机构标签 | v1 |
| `delete_school_label` | 删除机构标签 | v1 |

### 课程管理（7）
| 方法 | 说明 | 版本 |
|------|------|------|
| `add_course` | 创建课程 | v1 |
| `edit_course` | 编辑课程 | v1 |
| `end_course` | 结束课程 | v1 |
| `modify_course_teacher` | 更换课程（含所有未开始课节）老师 | v1 |
| `remove_course_teacher` | 移除课程老师 | v1 |
| `add_course_teacher_v2` | 添加班级老师 | v2 |
| `add_course_labels` | 添加/修改/删除课程标签 | v1 |

### 课节管理（6）
| 方法 | 说明 | 版本 |
|------|------|------|
| `add_course_class` | 创建课节 | v1 |
| `add_course_class_multiple` | 批量创建课节（含直播推流地址） | v1 |
| `edit_course_class` | 编辑课节 | v1 |
| `del_course_class` | 删除课节 | v1 |
| `modify_class_seatNum` | 修改课节上台人数 / 清晰度 | v1 |
| `add_class_labels` | 添加/修改/删除课节标签 | v1 |

### 课程 / 课节学生（7）
| 方法 | 说明 | 版本 |
|------|------|------|
| `add_course_student` | 添加课程学生 / 旁听 | v1 |
| `del_course_student` | 删除课程学生 / 旁听 | v1 |
| `add_course_student_multiple` | 批量添加课程学生 | v1 |
| `del_course_student_multiple` | 批量删除课程学生 | v1 |
| `add_class_student_multiple` | 课节批量添加学生 | v1 |
| `del_class_student_multiple` | 课节批量删除学生 | v1 |
| `add_course_class_student` | 课程下多个课节同时添加学生 | v1 |

### 课程分组（3）
| 方法 | 说明 | 版本 |
|------|------|------|
| `add_course_group` | 创建课程分组 | v1 |
| `edit_course_group` | 编辑课程分组 | v1 |
| `del_course_group` | 删除课程分组 | v1 |

### 直播 / 录播 / 回放（5）
| 方法 | 说明 | 版本 |
|------|------|------|
| `set_class_video_multiple` | 批量设置录课/直播/回放 | v1 |
| `delete_class_video` | 删除课节视频 / 回放文件 | v1 |
| `update_class_lock_status` | 锁定/解锁课节回放 | v1 |
| `get_webcast_url` | 获取直播 URL | v1 |
| `set_webcast` | LMS 课堂直播回放参数设置 | v2 |

### 云盘（9）
| 方法 | 说明 | 版本 |
|------|------|------|
| `get_folder_list` | 获取云盘根目录下两级文件夹列表 | v1 |
| `get_cloud_list` | 获取文件夹下文件列表 | v1 |
| `get_top_folder_id` | 获取机构云盘根目录 ID | v1 |
| `upload_file` | 上传文件（multipart） | v1 |
| `rename_file` | 重命名文件 | v1 |
| `del_file` | 删除文件 | v1 |
| `create_folder` | 创建文件夹 | v1 |
| `rename_folder` | 重命名文件夹 | v1 |
| `del_folder` | 删除文件夹 | v1 |

### LMS 单元 / 活动（11）
| 方法 | 说明 | 版本 |
|------|------|------|
| `create_unit` | 创建 LMS 单元 | v2 |
| `update_unit` | 编辑单元信息 | v2 |
| `delete_unit` | 删除单元 | v2 |
| `move_unit` | 移动单元下活动到目标单元 | v2 |
| `create_lms_lesson` | 创建 LMS 课堂活动 | v2 |
| `update_lms_lesson` | 编辑 LMS 课堂活动 | v2 |
| `create_activity_no_class` | 创建非课堂活动草稿（作业/测验/打卡等） | v2 |
| `release_activity` | 发布活动 | v2 |
| `delete_activity` | 删除活动 | v2 |
| `activity_add_student` | 添加活动成员 | v2 |
| `activity_delete_student` | 删除活动成员 | v2 |

### 在线双师（3）
| 方法 | 说明 | 版本 |
|------|------|------|
| `add_doubleTeacher_lesson` | 创建在线双师课堂 | v2 |
| `edit_doubleTeacher_lesson` | 编辑在线双师课堂 | v2 |
| `del_doubleTeacher_lesson` | 删除在线双师课堂 | v2 |

### 其他（4）
| 方法 | 说明 | 版本 |
|------|------|------|
| `get_login_linked` | 获取唤醒客户端进入教室链接 | v1 |
| `modify_group_member_nickname` | 用学生姓名同步班级群昵称 | v1 |
| `update_class_student_comment` | 更新课节教师对学生评价 | v1 |
| `edit_school_settings` | 修改学校设置（回放可见性等） | v2 |

</details>

## 上下文管理器

```python
with ClassInAPI(school_uid=123, school_secret="xxx") as api:
    api.add_course("test")
# 自动关闭底层 session，释放连接池
```

## 自动重试

SDK 默认在传输层**只对确认没到达服务端的失败**自动重试，因此对幂等和非幂等接口（含 `register`、`add_course_class_multiple`、`upload_file`）都安全 —— 不会重复落库 / 重复扣费：

| 失败类型 | 行为 |
|----------|------|
| 连接未建立（DNS / TCP / TLS 握手失败） | 自动重试，请求未到服务端，绝对安全 |
| 读阶段超时 / 中断（请求已送出） | **不重试**，无法判断服务端是否已处理 |
| HTTP 任意状态码（4xx / 5xx） | 不重试，由调用方根据业务语义决定 |
| 业务错误（响应体里 `errno != 1` / `code != 1`） | 不重试，原样返回响应体 |

通过构造参数调整或关闭：

```python
# 默认：3 次重试，指数退避 0.5s / 1s / 2s
api = ClassInAPI(school_uid=123, school_secret="xxx")

# 调高重试次数，更激进的退避
api = ClassInAPI(123, "xxx", max_retries=5, backoff_factor=1.0)

# 完全关闭重试
api = ClassInAPI(123, "xxx", max_retries=0)
```

重试耗尽后会抛 `EEONetworkError`，错误信息中包含「已重试仍失败」字样。

## 错误处理

当前版本的传输层只抛 `EEONetworkError`，**业务错误以响应体形式原样返回**（不抛 `EEOAPIError`），需要调用方自行检查 `errno` / `code` 字段：

```python
from eeo import EEONetworkError

try:
    resp = api.add_course("test")
except EEONetworkError as e:
    print(f"网络错误: {e}")
    return

# 业务结果校验（v1 / v2 字段不同）
err = resp.get("error_info") or {}
if err.get("errno", 1) != 1:
    print(f"v1 业务错误: {err.get('error')}")

# v2 接口若服务端返回 error_info，会被规整成：
# {"code": ..., "msg": ..., "data": ...}
if isinstance(resp, dict) and resp.get("code") not in (None, 1):
    print(f"v2 业务错误: {resp.get('msg')}")
```

异常类型一览：

| 异常 | 触发场景 |
|------|----------|
| `EEONetworkError` | 连接失败 / 超时 / 4xx / 5xx 等传输层错误 |
| `EEOAPIError` | 保留类型；业务错误检查目前在 SDK 内被关闭，由调用方判断 |
| `EEOSignatureError` | 保留类型；签名计算异常时使用 |
| `EEOError` | 上述三者的基类，便于一把 `except` 兜底 |

> 若需要 SDK 自动把业务错误抛成 `EEOAPIError`，参见 [eeo/utils.py](eeo/utils.py) 中 `_validate_v1_response` / `_validate_v2_response` 的注释代码块，取消注释即可。

## 自定义域名 / 超时

```python
api = ClassInAPI(
    school_uid=123,
    school_secret="xxx",
    domain="https://api.eeo.cn",       # 默认；海外节点改成对应域名
    timeout=(5, 60),                   # (connect, read)，秒
    upload_timeout=(5, 300),           # 上传单独超时
)
```

`ApiUrls.get_all_urls()` 可用于调试 —— 返回当前域名下所有已注册的端点。

## 开发

```bash
git clone https://github.com/EeoApiSupport/classin-sdk-py.git
cd classin-sdk-py

# 使用 uv（推荐）
uv venv
uv pip install -e .
uv pip install ruff pytest

# 或使用 pip
python -m venv .venv && source .venv/bin/activate
pip install -e .
pip install ruff pytest

# 代码风格 & 测试
ruff check eeo
pytest
```

## 最低要求

- Python ≥ 3.10
- requests ≥ 2.28.0

## 贡献

欢迎在 [GitHub Issues](https://github.com/EeoApiSupport/classin-sdk-py/issues) 反馈问题，或提交 PR。提交前请确认：

1. `ruff check eeo` 通过
2. `pytest` 全部通过（如有新增测试）
3. 新增公共方法补齐 docstring 和 README 接口列表

## 许可

[MIT](LICENSE) © ClassIn (EEO) API Support
