Skip to content

Commit d577809

Browse files
author
luk384090-cloud
committed
feat(v2): extend typed parsing, per-request options, and pagination fixes to all resources
- Extend fault-tolerant typed response parsing (parse_model) to every resource: apps, audio (asr/tts/customization/phrases), decision, deployments, fine_tunes, images, models, multimodal, reinforcement learning, rerank, tingwu, tokenization, and videos. Streamed frames and websocket results now degrade frame-by-frame instead of raising bare pydantic.ValidationError. - Expose max_retries as a per-request parameter on all resource methods, eliminating the need for a derived client to narrow retries for a single call. - Add raw=True support to all resource methods returning parsed models, allowing callers to obtain the raw APIResponse when needed. - Fix pagination traversal to advance from the request's page_no / page_size rather than the response echo, which may be missing or stale. BasePage.bind_pagination now accepts requested_page_no and requested_page_size. - Update request_options() to accept timeout and max_retries as keyword-only parameters. - Fix docs: correct pagination example (jobs.list → images.jobs.list), clarify streaming retry semantics and strict validation coverage. - Add comprehensive tests: per-request options forwarding for all resources, response drift coverage for every typed-parse surface, and pagination cursor correctness.
1 parent 520f4e6 commit d577809

29 files changed

Lines changed: 8435 additions & 496 deletions

‎README.md‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ for page in client.models.list(page_size=50).iter_pages(): # every page
200200
...
201201
```
202202

203-
On `AsyncClient` use `await page.next_page_async()` and `async for page in page.iter_pages_async()`. Calling the synchronous traversal on an async-fetched page raises `TypeError` naming the async method, rather than handing back an un-awaited coroutine. `client.files.list()` and `client.jobs.list()` paginate the same way.
203+
On `AsyncClient` use `await page.next_page_async()` and `async for page in page.iter_pages_async()`. Calling the synchronous traversal on an async-fetched page raises `TypeError` naming the async method, rather than handing back an un-awaited coroutine. `client.files.list()` and `client.images.jobs.list()` paginate the same way.
204204

205205
Serialize a page with `model_dump()`. Iterating a page yields its *items*, so `dict(page)` is not a way to read its fields.
206206

@@ -236,7 +236,7 @@ from dashscope.resources.embeddings import EmbeddingResponse
236236

237237
### Per-request options
238238

239-
Every resource method takes the same three escape hatches, for anything this SDK does not model yet:
239+
Every resource method that makes an HTTP request takes the same three escape hatches, for anything this SDK does not model yet:
240240

241241
```python
242242
client.chat.completions.create(
@@ -250,7 +250,14 @@ client.chat.completions.create(
250250

251251
`extra_headers` sits above every other layer — the SDK identity headers, the client defaults, your `headers=`, and a per-call `api_key=` / `workspace=` — matched case-insensitively. `extra_body` deep-merges nested dicts, so adding one nested field keeps its siblings; lists and scalars replace. A request whose body is not a JSON object (a file upload, raw bytes) has nothing to merge into, so `extra_body` is dropped with a warning rather than silently lost.
252252

253-
Retries stay a client-level setting; narrow them for a whole client with `client.with_options(max_retries=0)`.
253+
`max_retries` narrows or widens the retry budget for a single call, so a request that must not be replayed no longer needs a whole derived client:
254+
255+
```python
256+
client.deployments.list(max_retries=0) # one attempt, no retry
257+
client.with_options(max_retries=5) # still works, client-wide
258+
```
259+
260+
Streaming methods take all four: `open_stream` retries only while no event has reached you yet, so a mid-stream failure is never replayed. The websocket methods (`audio.asr.recognize` / `recognize_file`, `tingwu.realtime`) take none of them — that transport has no per-request options channel, so a parameter there would be silently inert.
254261

255262
### Streaming
256263

@@ -364,7 +371,7 @@ The three `DASHSCOPE_HTTP_*` pool variables are also settable as `http_max_conne
364371

365372
By default a response body that does not fit the SDK's typed models is parsed in degraded form — the offending fields fall back to their defaults and the rest of the payload stays usable — with the drift recorded at `DEBUG` on the `dashscope._logging.parse` logger. Set `DASHSCOPE_STRICT_VALIDATION=true` (or `Client(strict_validation=True)`, `settings.strict_validation = True`, `strict_validation = true` in `config.toml`) to raise `APIResponseValidationError` instead, which is what you want in CI or contract tests. Strict validation raises even under `raise_on_error=False`: that flag governs service errors, this one governs parse errors.
366373

367-
Two caveats. `DEBUG` is below the SDK's default log level, so a degraded parse is silent unless you raise it — see [Logging](#logging) — and `strict_validation` is the only way to make drift fail loudly. And the typed-parse path currently covers `chat.completions`, `models`, `files`, `jobs`, `images`, `embeddings` and `multimodal.embeddings`; the remaining surfaces (audio, fine-tuning, apps, decision, deployments, reinforcement learning, rerank, tokenization, tingwu) still construct their models directly and raise a plain `pydantic.ValidationError` on drift, ignoring both settings.
374+
One caveat: `DEBUG` is below the SDK's default log level, so a degraded parse is silent unless you raise it — see [Logging](#logging) — and `strict_validation` is the only way to make drift fail loudly. The typed-parse path covers every resource, and not only the final response object: streamed frames (`apps`, `deployments.stream_events`, `fine_tunes.stream_events`, `audio.tts.http.stream`) and websocket results (`audio.asr`) degrade frame by frame as well, so one malformed event cannot break an iteration partway through with a `pydantic.ValidationError` that no `except DashScopeError` would catch.
368375

369376
**Config file** (`~/.dashscope/config.toml`):
370377

‎README_zh.md‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ for page in client.models.list(page_size=50).iter_pages(): # 遍历所有页
200200
...
201201
```
202202

203-
`AsyncClient` 上请用 `await page.next_page_async()` 和 `async for page in page.iter_pages_async()`。对异步取回的页面调用同步翻页方法会抛出 `TypeError` 并指明该用哪个异步方法,而不是把一个未 await 的 coroutine 交给你。`client.files.list()` 和 `client.jobs.list()` 的分页方式相同。
203+
`AsyncClient` 上请用 `await page.next_page_async()` 和 `async for page in page.iter_pages_async()`。对异步取回的页面调用同步翻页方法会抛出 `TypeError` 并指明该用哪个异步方法,而不是把一个未 await 的 coroutine 交给你。`client.files.list()` 和 `client.images.jobs.list()` 的分页方式相同。
204204

205205
序列化页面请用 `model_dump()`。迭代页面对象得到的是它的**条目**,所以 `dict(page)` 不是读取字段的方式。
206206

@@ -236,7 +236,7 @@ from dashscope.resources.embeddings import EmbeddingResponse
236236

237237
### 单次请求定制
238238

239-
每个资源方法都接受同样的三个逃生舱,用于承载 SDK 尚未建模的东西:
239+
所有发起 HTTP 请求的资源方法都接受同样的三个逃生舱,用于承载 SDK 尚未建模的东西:
240240

241241
```python
242242
client.chat.completions.create(
@@ -250,7 +250,14 @@ client.chat.completions.create(
250250

251251
`extra_headers` 位于所有其他层之上——SDK 身份头、客户端默认头、你的 `headers=`、以及单次调用的 `api_key=` / `workspace=`——且大小写不敏感匹配。`extra_body` 对嵌套 dict 做深合并,所以新增一个嵌套字段不会丢掉它的兄弟字段;列表和标量则是整体替换。若请求体不是 JSON 对象(文件上传、裸字节),就无处可合并,此时 `extra_body` 会被丢弃并告警,而不是静默丢失。
252252

253-
重试仍是客户端级设置;要为整个客户端收窄,用 `client.with_options(max_retries=0)`。
253+
`max_retries` 为单次调用收窄或放宽重试预算,所以一个绝不能被重放的请求不再需要派生整个客户端:
254+
255+
```python
256+
client.deployments.list(max_retries=0) # 只打一次,不重试
257+
client.with_options(max_retries=5) # 依然有效,作用于整个客户端
258+
```
259+
260+
流式方法四个都接受:`open_stream` 只在还没有任何事件到达你之前重试,所以流中途的失败绝不会被重放。WebSocket 方法(`audio.asr.recognize` / `recognize_file`、`tingwu.realtime`)一个都不接受——那条传输链路根本没有单次请求的 options 通道,加了形参也只会被静默忽略。
254261

255262
### 流式
256263

@@ -364,7 +371,7 @@ dashscope.settings.log_level = "INFO"
364371

365372
默认情况下,若响应体与 SDK 的类型模型不符,会走降级解析——出问题的字段回退到各自的默认值,报文中其余部分照常可用——同时把漂移以 `DEBUG` 级别记录到 `dashscope._logging.parse` 这个 logger。设置 `DASHSCOPE_STRICT_VALIDATION=true`(或 `Client(strict_validation=True)`、`settings.strict_validation = True`、`config.toml` 里的 `strict_validation = true`)则改为抛出 `APIResponseValidationError`,这正是 CI 或契约测试想要的行为。严格校验即使在 `raise_on_error=False` 下也会抛错:那个开关管的是服务端错误,这个管的是解析错误。
366373

367-
两点需要注意。其一,`DEBUG` 低于 SDK 的默认日志级别,所以除非你调高它(见[日志](#日志)),降级解析是无声的——而 `strict_validation` 是让漂移大声失败的唯一手段。其二,类型化解析路径目前覆盖 `chat.completions`、`models`、`files`、`jobs`、`images`、`embeddings` 和 `multimodal.embeddings`;其余接口(语音、微调、应用、决策、部署、强化学习、重排、Token 计数、听悟)仍是直接构造模型,遇到漂移会抛出原始的 `pydantic.ValidationError`,两个开关都不生效。
374+
一点需要注意:`DEBUG` 低于 SDK 的默认日志级别,所以除非你调高它(见[日志](#日志)),降级解析是无声的——而 `strict_validation` 是让漂移大声失败的唯一手段。类型化解析路径覆盖全部资源,且不止最终的响应对象:流式帧(`apps`、`deployments.stream_events`、`fine_tunes.stream_events`、`audio.tts.http.stream`)和 WebSocket 结果(`audio.asr`)同样逐帧降级,因此单个畸形事件不会在迭代中途抛出 `except DashScopeError` 抓不到的 `pydantic.ValidationError`。
368375

369376
**配置文件**(`~/.dashscope/config.toml`):
370377

0 commit comments

Comments
 (0)