Metadata-Version: 2.4
Name: mingdao-hap-python-sdk
Version: 0.1.0
Summary: 明道云 HAP API V3 Python SDK — ThinkPHP 风格链式调用
Author: mingdao-hap-sdk contributors
License: MIT
Project-URL: Homepage, https://github.com/your-org/mingdao-hap-python-sdk
Project-URL: Documentation, https://apidoc.mingdao.com/application_v3/appkey-sign/zh-Hans
Keywords: mingdao,hap,api,sdk,low-code
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx<1.0,>=0.24
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: pytest-httpx>=0.30; extra == "dev"

# 明道云 HAP Python SDK

明道云 HAP API V3 Python SDK，提供 ThinkPHP 风格的链式调用方式，支持同步与异步操作。

## 安装

```bash
pip install mingdao-hap-python-sdk
```

## 快速开始

```python
from mingdao_hap import MdClient

# 创建客户端
client = MdClient(
    app_key="your_app_key",
    sign="your_sign",
)

# 获取工作表
sheet = client.worksheet("your_worksheet_id")

# 查询数据
rows = sheet.where("status", "eq", "active").orderBy("created_at").limit(10).get()

# 插入数据
sheet.insert({"name": "张三", "age": 25, "status": "active"})

# 更新数据
sheet.where("name", "eq", "张三").update({"age": 26})

# 删除数据
sheet.where("status", "eq", "inactive").delete()

# 统计数据
total = sheet.where("status", "eq", "active").count()
```

## 客户端配置

`MdClient` 支持以下参数：

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `app_key` | `str` | **必填** | 应用 AppKey |
| `sign` | `str` | **必填** | 应用 Sign |
| `base_url` | `str` | `https://api.mingdao.com` | API 基地址 |
| `timeout` | `float` | `30.0` | 请求超时秒数 |
| `max_retries` | `int` | `3` | 最大重试次数（指数退避） |
| `qps` | `float` | `50` | 每秒最大请求数限制 |

### 上下文管理器

支持 `with` 语法自动关闭连接：

```python
with MdClient(app_key="...", sign="...") as client:
    result = client.worksheet("sheet_id").get()
```

异步同样支持：

```python
async with MdClient(app_key="...", sign="...") as client:
    result = await client.worksheet("sheet_id").getAsync()
```

## 查询构造器

通过 `client.worksheet(worksheet_id)` 获取 `QueryBuilder` 实例，支持链式调用。**每次调用均返回全新实例，条件不会跨请求污染。**

### 筛选条件

#### where — AND 条件

```python
from mingdao_hap import MdClient

client = MdClient(app_key="...", sign="...")
sheet = client.worksheet("sheet_id")

# 等值查询
sheet.where("name", "eq", "张三")

# 多条件 AND 连接
sheet.where("name", "eq", "张三").where("age", "gt", 18)
```

支持的运算符：

| 运算符 | 说明 |
|--------|------|
| `eq` | 等于 |
| `ne` | 不等于 |
| `in` | 包含于 |
| `notin` | 不包含于 |
| `contains` | 包含文本 |
| `gt` | 大于 |
| `gte` | 大于等于 |
| `lt` | 小于 |
| `lte` | 小于等于 |
| `between` | 区间 |
| `isempty` | 为空（无需传 value） |
| `isnotempty` | 不为空（无需传 value） |

#### orWhere — OR 条件

```python
# OR 条件连接
sheet.where("status", "eq", "active").orWhere("priority", "gte", 1)

# 注意：where 和 orWhere 混用时，所有条件按调用的先后顺序用 OR 连接
```

#### whereRaw — 原始筛选条件

直接传入完整的 HAP Filter JSON 对象，**会清空此前通过 where/orWhere 设置的条件**：

```python
sheet.whereRaw({
    "type": "group",
    "logic": "AND",
    "children": [
        {"type": "condition", "field": "status", "operator": "eq", "value": "active"},
        {
            "type": "group",
            "logic": "OR",
            "children": [
                {"type": "condition", "field": "priority", "operator": "gte", "value": 1},
                {"type": "condition", "field": "rank", "operator": "lte", "value": 10},
            ],
        },
    ],
})
```

### 排序

```python
# 降序（默认）
sheet.orderBy("created_at")

# 升序
sheet.orderBy("name", "asc")

# 降序
sheet.orderBy("created_at", "desc")
```

### 分页

```python
# 限制返回条数
sheet.limit(10)

# 偏移量
sheet.offset(20)

# 分页快捷方法（page 从 1 开始）
sheet.page(2, 10)  # 第2页，每页10条
```

### 字段选择

```python
# 只返回指定字段
sheet.select("name", "age", "status")

# 清除字段选择，返回全部字段
sheet.select()
```

### 链式调用示例

```python
result = (
    client.worksheet("sheet_id")
    .where("status", "eq", "active")
    .where("age", "gte", 18)
    .orderBy("created_at", "desc")
    .select("name", "age")
    .limit(10)
    .get()
)
```

## 查询操作

所有终端方法在执行后**自动重置**查询条件。

### get — 获取行列表

```python
rows = sheet.where("status", "eq", "active").get()
# 返回: [{"rowId": "xxx", "name": "张三", ...}, ...]
```

### first — 获取第一条

```python
row = sheet.where("name", "eq", "张三").first()
# 返回: {"rowId": "xxx", "name": "张三", ...} 或 None
```

### count — 统计数量

```python
total = sheet.where("status", "eq", "active").count()
# 返回: 42
```

## 写入操作

### insert — 插入单条

```python
record = sheet.insert({"name": "张三", "age": 25, "status": "active"})
```

### insertAll — 批量插入

```python
records = sheet.insertAll([
    {"name": "张三", "age": 25},
    {"name": "李四", "age": 30},
    {"name": "王五", "age": 28},
], trigger_workflow=True)  # trigger_workflow=False 禁用工作流
```

### update — 按条件批量更新

> **必须先调用 `where` 设置条件**，否则抛出 `MdBuilderError`，防止意外全表更新。

```python
sheet.where("status", "eq", "draft").update({"status": "published"})
```

### updateById — 按行 ID 更新

```python
sheet.updateById("row_id_here", {"status": "published"})
```

### delete — 按条件批量删除

> **必须先调用 `where` 设置条件**，否则抛出 `MdBuilderError`，防止意外全表删除。

```python
sheet.where("status", "eq", "inactive").delete()
```

### deleteById — 按行 ID 删除

```python
sheet.deleteById("row_id_here")
```

## 异步操作

所有查询和写入方法都有对应的异步版本，方法名以 `Async` 结尾：

```python
import asyncio

async def main():
    async with MdClient(app_key="...", sign="...") as client:
        sheet = client.worksheet("sheet_id")

        # 异步查询
        rows = await sheet.where("status", "eq", "active").getAsync()
        first = await sheet.where("name", "eq", "张三").firstAsync()
        total = await sheet.where("status", "eq", "active").countAsync()

        # 异步写入
        await sheet.insertAsync({"name": "张三"})
        await sheet.insertAllAsync([{"name": "李四"}, {"name": "王五"}])
        await sheet.where("name", "eq", "张三").updateAsync({"age": 26})
        await sheet.updateByIdAsync("row_id", {"age": 26})
        await sheet.where("status", "eq", "inactive").deleteAsync()
        await sheet.deleteByIdAsync("row_id")

asyncio.run(main())
```

## 异常处理

SDK 定义了以下异常类，均继承自 `MdError`：

| 异常类 | 说明 |
|--------|------|
| `MdError` | 基础异常 |
| `MdConfigError` | 配置错误（如未提供 app_key / sign） |
| `MdApiError` | API 返回的业务错误 |
| `MdNetworkError` | 网络连接/超时错误（重试耗尽后） |
| `MdRateLimitError` | QPS 限流重试耗尽后 |
| `MdBuilderError` | 查询构造器使用错误（如 update/delete 前未设置 where） |

`MdApiError` 包含以下属性：

```python
try:
    sheet.where("status", "eq", "active").get()
except MdApiError as e:
    print(f"HTTP状态码: {e.status_code}")
    print(f"错误码: {e.code}")
    print(f"错误信息: {e.message}")
    print(f"请求ID: {e.request_id}")
```

### 重试机制

SDK 在以下情况下自动重试（指数退避，最大 60 秒）：

- 网络连接错误
- 请求超时
- HTTP 503 服务不可用

```python
# 关闭自动重试
client = MdClient(app_key="...", sign="...", max_retries=0)
```

## 完整示例

```python
from mingdao_hap import MdClient, MdApiError, MdBuilderError

# 初始化客户端
client = MdClient(
    app_key="your_app_key",
    sign="your_sign",
    base_url="https://api.mingdao.com",
    timeout=10,
    max_retries=2,
    qps=10,
)

sheet = client.worksheet("your_worksheet_id")

# 1. 插入测试数据
print("插入数据...")
sheet.insert({"name": "sdk-test", "amount": 100})

# 2. 条件查询
print("查询数据...")
rows = sheet.where("name", "eq", "sdk-test").get()
print(f"找到 {len(rows)} 条记录")

# 3. 更新数据
if rows:
    count = sheet.where("name", "eq", "sdk-test").update({"amount": 200})
    print(f"更新了 {count['updated']} 条记录")

# 4. 获取单条
row = sheet.where("name", "eq", "sdk-test").first()
if row:
    print(f"rowId: {row['rowId']}, amount: {row.get('amount')}")

# 5. 统计数量
total = sheet.where("name", "eq", "sdk-test").count()
print(f"共 {total} 条匹配记录")

# 6. 清理测试数据
deleted = sheet.where("name", "eq", "sdk-test").delete()
print(f"删除了 {deleted['deleted']} 条记录")

# 7. 验证删除
assert sheet.where("name", "eq", "sdk-test").count() == 0
print("数据清理完成")
```

## API 映射参考

SDK 方法与 HAP API V3 端点的对应关系：

| SDK 方法 | HTTP | 端点 |
|----------|------|------|
| `get()` | POST | `/v3/app/worksheets/{id}/rows/list` |
| `first()` | POST | `/v3/app/worksheets/{id}/rows/list` |
| `count()` | POST | `/v3/app/worksheets/{id}/rows/list` |
| `insert()` | POST | `/v3/app/worksheets/{id}/rows` |
| `insertAll()` | POST | `/v3/app/worksheets/{id}/rows` |
| `update()` | PATCH | `/v3/app/worksheets/{id}/rows/{rowId}` |
| `updateById()` | PATCH | `/v3/app/worksheets/{id}/rows/{rowId}` |
| `delete()` | DELETE | `/v3/app/worksheets/{id}/rows/batch` |
| `deleteById()` | DELETE | `/v3/app/worksheets/{id}/rows/{rowId}` |

## 许可证

MIT
