typact 0.2.1__tar.gz → 0.2.2a2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. typact-0.2.2a2/CHANGELOG.md +74 -0
  2. typact-0.2.2a2/CONTRIBUTING.md +35 -0
  3. typact-0.2.2a2/MANIFEST.in +7 -0
  4. typact-0.2.2a2/PKG-INFO +324 -0
  5. typact-0.2.2a2/README.md +292 -0
  6. typact-0.2.2a2/README.zh-CN.md +687 -0
  7. typact-0.2.2a2/RELEASING.md +45 -0
  8. typact-0.2.2a2/pyproject.toml +62 -0
  9. {typact-0.2.1 → typact-0.2.2a2}/src/typact/__init__.py +6 -1
  10. {typact-0.2.1 → typact-0.2.2a2}/src/typact/client/http_client.py +117 -28
  11. typact-0.2.2a2/src/typact/core/events.py +19 -0
  12. typact-0.2.2a2/src/typact/core/retry.py +61 -0
  13. typact-0.2.2a2/src/typact/py.typed +0 -0
  14. typact-0.2.2a2/src/typact.egg-info/PKG-INFO +324 -0
  15. {typact-0.2.1 → typact-0.2.2a2}/src/typact.egg-info/SOURCES.txt +8 -0
  16. {typact-0.2.1 → typact-0.2.2a2}/tests/test_http_client_request.py +67 -0
  17. typact-0.2.2a2/tests/test_response_retry.py +180 -0
  18. {typact-0.2.1 → typact-0.2.2a2}/tests/test_runtime_integration.py +77 -9
  19. typact-0.2.1/PKG-INFO +0 -596
  20. typact-0.2.1/README.md +0 -581
  21. typact-0.2.1/pyproject.toml +0 -25
  22. typact-0.2.1/src/typact/core/retry.py +0 -28
  23. typact-0.2.1/src/typact.egg-info/PKG-INFO +0 -596
  24. {typact-0.2.1 → typact-0.2.2a2}/LICENSE +0 -0
  25. {typact-0.2.1 → typact-0.2.2a2}/setup.cfg +0 -0
  26. {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/__init__.py +0 -0
  27. {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/base.py +0 -0
  28. {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/body.py +0 -0
  29. {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/cookie.py +0 -0
  30. {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/file.py +0 -0
  31. {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/form.py +0 -0
  32. {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/header.py +0 -0
  33. {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/path.py +0 -0
  34. {typact-0.2.1 → typact-0.2.2a2}/src/typact/annotations/query.py +0 -0
  35. {typact-0.2.1 → typact-0.2.2a2}/src/typact/builder/__init__.py +0 -0
  36. {typact-0.2.1 → typact-0.2.2a2}/src/typact/builder/multipart_builder.py +0 -0
  37. {typact-0.2.1 → typact-0.2.2a2}/src/typact/builder/request_builder.py +0 -0
  38. {typact-0.2.1 → typact-0.2.2a2}/src/typact/builder/url_builder.py +0 -0
  39. {typact-0.2.1 → typact-0.2.2a2}/src/typact/client/__init__.py +0 -0
  40. {typact-0.2.1 → typact-0.2.2a2}/src/typact/client/decorator.py +0 -0
  41. {typact-0.2.1 → typact-0.2.2a2}/src/typact/client/metadata.py +0 -0
  42. {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/__init__.py +0 -0
  43. {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/file_converter.py +0 -0
  44. {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/json_converter.py +0 -0
  45. {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/response_converter.py +0 -0
  46. {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/sse_converter.py +0 -0
  47. {typact-0.2.1 → typact-0.2.2a2}/src/typact/converter/stream_converter.py +0 -0
  48. {typact-0.2.1 → typact-0.2.2a2}/src/typact/core/__init__.py +0 -0
  49. {typact-0.2.1 → typact-0.2.2a2}/src/typact/core/errors.py +0 -0
  50. {typact-0.2.1 → typact-0.2.2a2}/src/typact/core/types.py +0 -0
  51. {typact-0.2.1 → typact-0.2.2a2}/src/typact/interceptor/__init__.py +0 -0
  52. {typact-0.2.1 → typact-0.2.2a2}/src/typact/interceptor/auth.py +0 -0
  53. {typact-0.2.1 → typact-0.2.2a2}/src/typact/interceptor/base.py +0 -0
  54. {typact-0.2.1 → typact-0.2.2a2}/src/typact/interceptor/log.py +0 -0
  55. {typact-0.2.1 → typact-0.2.2a2}/src/typact/interceptor/trace.py +0 -0
  56. {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/__init__.py +0 -0
  57. {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/aiohttp.py +0 -0
  58. {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/base.py +0 -0
  59. {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/httpx.py +0 -0
  60. {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/mock.py +0 -0
  61. {typact-0.2.1 → typact-0.2.2a2}/src/typact/runtime/urllib.py +0 -0
  62. {typact-0.2.1 → typact-0.2.2a2}/src/typact/testing/__init__.py +0 -0
  63. {typact-0.2.1 → typact-0.2.2a2}/src/typact/testing/mock_runtime.py +0 -0
  64. {typact-0.2.1 → typact-0.2.2a2}/src/typact.egg-info/dependency_links.txt +0 -0
  65. {typact-0.2.1 → typact-0.2.2a2}/src/typact.egg-info/requires.txt +0 -0
  66. {typact-0.2.1 → typact-0.2.2a2}/src/typact.egg-info/top_level.txt +0 -0
@@ -0,0 +1,74 @@
1
+ # Changelog
2
+
3
+ 本文件区分未发布改动与已在 PyPI 发布的版本。当前预发布版本为 0.2.2a2,正式版 0.2.2 尚未发布。
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.2.2a2] - 2026-09-22
8
+
9
+ ### 变更
10
+
11
+ - 最低 Python 版本从 3.13 下调至 3.10,并在 Linux 与 Windows CI 中覆盖 Python 3.10 至 3.14。
12
+ - 英文 README 和文档成为默认入口,原中文 README 保留为 `README.zh-CN.md`。
13
+
14
+ ## [0.2.2a1] - 2026-09-14
15
+
16
+ ### 新增
17
+
18
+ - 请求生命周期事件:`request`、`retry`、`response`、`failure`;支持同步和异步监听器。
19
+ - `RetryConfig.should_retry_response`:对完整 `typact.Response` 执行同步布尔判断,支持业务响应码重试。
20
+ - 公开 `default_should_retry_response`,允许自定义策略复用默认 HTTP 状态码规则。
21
+ - GitHub Actions 测试、安装包与文档检查;手动准备 Draft Release 的工作流。
22
+ - `py.typed`、项目链接、贡献指南、发布规范与历史发行审计记录。
23
+
24
+ ### 行为说明
25
+
26
+ - 原有 `retry_status_codes` 保持支持;同时指定非 None 的状态码集合和响应回调会报配置错误。
27
+ - 回调完全接管普通响应判断,仍受方法、次数和退避限制;回调错误直接传播。
28
+ - 流式/SSE 请求不支持完整响应回调,需路由级状态码策略或关闭重试。原有首分片前重试规则不变。
29
+
30
+ ### 工程调整
31
+
32
+ - 流式集成测试按完整字节内容验证,避免依赖不同平台与 Runtime 的网络分块边界。
33
+ - 测试服务器在客户端提前断开时清理连接,避免 Windows CI 拆除服务器时挂起;测试增加有界超时诊断。
34
+ - 标准文档构建与 Sites 专用打包分离;使用 Node 22 和固定前端依赖版本。
35
+ - 修正文档 canonical 站点地址,增加 AtomGit/GitHub 托管入口。
36
+ - 补录历史附注标签与 GitHub Releases,不代表发布了新的 PyPI 版本。
37
+
38
+ ## [0.2.1] - 2026-08-31
39
+
40
+ - 新增路由级超时和重试策略覆盖,适用于 HTTP 装饰器与 `client.request()`。
41
+ - 未指定时继承 Client 配置,显式 `None` 关闭对应继承。
42
+ - 流式请求仅在第一个分片交付前允许重试。
43
+
44
+ ## [0.2.0] - 2026-08-17
45
+
46
+ - 请求超时、网络异常与状态码重试,使用指数退避。
47
+ - SSE 与普通流式响应转换。
48
+ - 修正 httpx Runtime 的 Cookie 处理。
49
+
50
+ ## [0.2.0a1] - 2026-08-13
51
+
52
+ - 0.2.0 系列测试发行。发布包的 Python 源码与后续 0.2.0 一致。
53
+ - 历史 Git 没有相同版本元数据的提交;标签使用经校验的 PyPI sdist 恢复快照。
54
+
55
+ ## [0.1.4] - 2026-07-24
56
+
57
+ - 统一公共 `Response` 类型,保留 `SimpleResponse` 兼容别名。
58
+ - 支持原始响应、文本与字节下载。
59
+
60
+ ## [0.1.3] - 2026-07-14
61
+
62
+ - 早期发行修订,与 0.1.2 的 Python 源码相同。
63
+ - 历史标签使用 PyPI sdist 恢复快照,不将版本元数据修订描述为新增功能。
64
+
65
+ ## [0.1.2] - 2026-07-14
66
+
67
+ - `FileData` 承载文件内容、文件名和媒体类型,`File` 负责参数声明。
68
+ - MIT 许可证。历史标签使用 PyPI sdist 恢复快照。
69
+
70
+ ## [0.1.1] - 2026-07-14
71
+
72
+ - 首个保留在 PyPI 的 Typact 版本,支持声明式 HTTP 请求、参数注解、响应转换、多 Runtime 和认证扩展。
73
+
74
+ 历史日期采用 PyPI 首次上传日期。版本映射、原始 sdist 地址及哈希见 [发行审计记录](docs/release-history.json)。原始发布包是历史事实来源;恢复快照不代表找到了原始发布提交。
@@ -0,0 +1,35 @@
1
+ # 贡献指南
2
+
3
+ Typact 是开源 HTTP 客户端库。设计变更应考虑通用场景、公共 API、可选依赖、不同 Runtime 的一致性,以及已有用户的升级成本。
4
+
5
+ ## 开发环境
6
+
7
+ Python 3.10 至 3.14;使用 uv 管理环境:
8
+
9
+ ```sh
10
+ uv sync --all-extras --group dev
11
+ uv run --no-sync pytest -q
12
+ ```
13
+
14
+ CI 在 Windows 和 Linux 上运行上述 Python 版本的完整测试,包含 urllib、httpx 和 aiohttp。Python 元数据允许更新版本,但未经 CI 验证的版本不在当前保证范围内。
15
+
16
+ 文档使用 Node.js 22,版本记录在 `docs-site/.node-version`:
17
+
18
+ ```sh
19
+ cd docs-site
20
+ npm ci
21
+ npm run build
22
+ ```
23
+
24
+ 标准静态构建不需要 Sites 配置;仅在已配置 `.openai/hosting.json` 的专用环境中使用 `npm run build:sites`。Netlify 使用同一标准构建命令。Windows 上请使用约定的 Node 22,避免已观察到的 Node 24.19.0 退出断言。
25
+
26
+ ## 提交要求
27
+
28
+ - 每次变更聚焦一个问题,新增公开行为需补文档和边界测试。
29
+ - 修改重试、认证、超时、取消或流式行为时,说明组合语义与资源生命周期。
30
+ - 保持可选 HTTP 依赖可选;核心安装不可因新增功能而强制导入它们。
31
+ - 将用户可见变化写入 `CHANGELOG.md` 的 Unreleased 部分。
32
+ - 公共 API 的删除、更名或默认语义变化必须说明迁移方式;不要用实现更简单作为唯一理由。
33
+ - 不提交凭据、依赖缓存或生成的构建产物。不要改写已公开的版本标签。
34
+
35
+ 发布流程见 [RELEASING.md](RELEASING.md)。
@@ -0,0 +1,7 @@
1
+ include README.md README.zh-CN.md LICENSE CHANGELOG.md CONTRIBUTING.md RELEASING.md
2
+ recursive-include src/typact *.py py.typed
3
+ recursive-include tests *.py
4
+ prune docs-site
5
+ prune .release-audit
6
+ prune .publish-dist
7
+ global-exclude __pycache__ *.py[cod]
@@ -0,0 +1,324 @@
1
+ Metadata-Version: 2.4
2
+ Name: typact
3
+ Version: 0.2.2a2
4
+ Summary: Build production-ready Python API clients with FastAPI-style declarations.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://typact.zzq.jl.cn
7
+ Project-URL: Documentation, https://typact.zzq.jl.cn/docs/getting-started/
8
+ Project-URL: GitHub, https://github.com/zzqjlcn/typact
9
+ Project-URL: AtomGit, https://atomgit.com/zhangzhanqi/typact
10
+ Project-URL: Issues, https://github.com/zzqjlcn/typact/issues
11
+ Project-URL: Changelog, https://github.com/zzqjlcn/typact/blob/main/CHANGELOG.md
12
+ Keywords: http,api-client,async,typing,declarative,fastapi,pydantic,sse,retry
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Framework :: AsyncIO
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: pydantic>=2.13.4
27
+ Provides-Extra: aiohttp
28
+ Requires-Dist: aiohttp>=3.14.1; extra == "aiohttp"
29
+ Provides-Extra: httpx
30
+ Requires-Dist: httpx>=0.28.1; extra == "httpx"
31
+ Dynamic: license-file
32
+
33
+ # <img src="https://typact.zzq.jl.cn/favicon.svg" alt="Typact" width="28" height="28" style="vertical-align: middle;"/> Typact
34
+
35
+ [![Documentation](https://img.shields.io/badge/docs-typact.zzq.jl.cn-blue)](https://typact.zzq.jl.cn)
36
+ [![PyPI](https://img.shields.io/pypi/v/typact)](https://pypi.org/project/typact/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/typact)](https://pypi.org/project/typact/)
38
+ [![CI](https://github.com/zzqjlcn/typact/actions/workflows/ci.yml/badge.svg)](https://github.com/zzqjlcn/typact/actions/workflows/ci.yml)
39
+ [![License](https://img.shields.io/pypi/l/typact)](https://github.com/zzqjlcn/typact/blob/main/LICENSE)
40
+
41
+ [Documentation](https://typact.zzq.jl.cn) · [PyPI](https://pypi.org/project/typact/) · [中文 README](https://github.com/zzqjlcn/typact/blob/main/README.zh-CN.md) · [Changelog](https://github.com/zzqjlcn/typact/blob/main/CHANGELOG.md)
42
+
43
+ **Build production-ready Python API clients with FastAPI-style declarations.**
44
+
45
+ Typact turns an annotated Python function into an executable HTTP contract. Declare the request parameters and return type once; Typact handles request building, transport, response validation, retries, authentication, streaming, and test doubles.
46
+
47
+ ```python
48
+ from pydantic import BaseModel
49
+
50
+ from typact import HttpClient, Path
51
+
52
+
53
+ class User(BaseModel):
54
+ id: int
55
+ name: str
56
+
57
+
58
+ client = HttpClient("https://api.example.com")
59
+
60
+
61
+ @client.get("/users/{user_id}")
62
+ async def get_user(user_id: int = Path()) -> User:
63
+ pass
64
+
65
+
66
+ user = await get_user(1)
67
+ ```
68
+
69
+ ## Why Typact?
70
+
71
+ Handwritten clients tend to repeat the same plumbing: assemble URLs and headers, serialize bodies, check status codes, validate JSON, retry transient failures, refresh tokens, and mock the network in tests. Typact keeps that behavior behind one typed contract.
72
+
73
+ - **FastAPI-style declarations** with `Path`, `Query`, `Header`, `Cookie`, `Body`, `Form`, and `File`.
74
+ - **Validated return types** powered by Pydantic v2 `TypeAdapter`.
75
+ - **Production request policies** including timeouts, exponential backoff, response-aware retries, and token refresh.
76
+ - **Streaming support** for raw byte/text streams and typed Server-Sent Events.
77
+ - **Pluggable transports** using the standard library, httpx, aiohttp, or a custom Runtime.
78
+ - **Deterministic tests** through a Mock Runtime that records the request Typact built.
79
+ - **Small core install** with Pydantic as the only required dependency.
80
+
81
+ Typact is async-first and targets Python 3.10 through 3.14.
82
+
83
+ ## Installation
84
+
85
+ The default Runtime uses Python's standard library:
86
+
87
+ ```bash
88
+ pip install typact
89
+ ```
90
+
91
+ Install an optional transport when you need httpx or aiohttp:
92
+
93
+ ```bash
94
+ pip install "typact[httpx]"
95
+ pip install "typact[aiohttp]"
96
+ ```
97
+
98
+ ## Quick start
99
+
100
+ ```python
101
+ import asyncio
102
+
103
+ from pydantic import BaseModel, ConfigDict, Field
104
+
105
+ from typact import Body, HttpClient, Path, Query
106
+
107
+
108
+ class Todo(BaseModel):
109
+ model_config = ConfigDict(populate_by_name=True)
110
+
111
+ user_id: int = Field(alias="userId")
112
+ id: int | None = None
113
+ title: str
114
+ completed: bool
115
+
116
+
117
+ client = HttpClient("https://jsonplaceholder.typicode.com")
118
+
119
+
120
+ @client.get("/todos/{todo_id}")
121
+ async def get_todo(todo_id: int = Path()) -> Todo:
122
+ pass
123
+
124
+
125
+ @client.get("/todos")
126
+ async def list_todos(user_id: int = Query(alias="userId")) -> list[Todo]:
127
+ pass
128
+
129
+
130
+ @client.post("/todos")
131
+ async def create_todo(todo: Todo = Body()) -> Todo:
132
+ pass
133
+
134
+
135
+ async def main():
136
+ todo = await get_todo(1)
137
+ todos = await list_todos(user_id=1)
138
+ created = await create_todo(
139
+ Todo(user_id=1, title="Try Typact", completed=False)
140
+ )
141
+ print(todo, todos[0], created)
142
+ await client.close()
143
+
144
+
145
+ asyncio.run(main())
146
+ ```
147
+
148
+ ## Production request policies
149
+
150
+ Configure shared timeout and retry behavior on the client, then override it per route when an endpoint needs different semantics:
151
+
152
+ ```python
153
+ from typact import HttpClient, Path, RetryConfig
154
+
155
+
156
+ client = HttpClient(
157
+ "https://api.example.com",
158
+ timeout=10,
159
+ retry_config=RetryConfig(max_retries=3, initial_delay=0.5),
160
+ )
161
+
162
+
163
+ @client.get(
164
+ "/reports/{report_id}",
165
+ timeout=60,
166
+ retry_config=RetryConfig(max_retries=2),
167
+ )
168
+ async def get_report(report_id: int = Path()) -> dict:
169
+ pass
170
+ ```
171
+
172
+ Retries are disabled by default. When enabled, the default policy retries network and timeout failures plus `429`, `502`, `503`, and `504` responses for idempotent methods.
173
+
174
+ Typact can also retry a successful HTTP response that carries a transient business error:
175
+
176
+ ```python
177
+ from typact import Response, RetryConfig, default_should_retry_response
178
+
179
+
180
+ def should_retry(response: Response) -> bool:
181
+ if default_should_retry_response(response):
182
+ return True
183
+ data = response.json()
184
+ return (
185
+ response.status_code == 200
186
+ and isinstance(data, dict)
187
+ and data.get("code") in {"SYSTEM_BUSY", "RATE_LIMITED"}
188
+ )
189
+
190
+
191
+ client = HttpClient(
192
+ "https://api.example.com",
193
+ retry_config=RetryConfig(
194
+ max_retries=3,
195
+ should_retry_response=should_retry,
196
+ ),
197
+ )
198
+ ```
199
+
200
+ See the [Runtime guide](https://typact.zzq.jl.cn/docs/runtime/) for inheritance rules, streaming behavior, and lifecycle events.
201
+
202
+ ## Authentication and cross-cutting behavior
203
+
204
+ Interceptors keep authentication, logging, and tracing out of endpoint declarations:
205
+
206
+ ```python
207
+ from typact import BearerTokenInterceptor, HttpClient, InterceptorChain
208
+
209
+
210
+ client = HttpClient(
211
+ "https://api.example.com",
212
+ interceptor_chain=InterceptorChain(
213
+ request_interceptors=[BearerTokenInterceptor("your-token")],
214
+ ),
215
+ )
216
+ ```
217
+
218
+ Built-in interceptors include bearer tokens, refreshable bearer tokens, API keys, trace IDs, and logging.
219
+
220
+ ## Files and streaming
221
+
222
+ Upload multipart files with `FileData`:
223
+
224
+ ```python
225
+ from typact import File, FileData
226
+
227
+
228
+ @client.post("/upload")
229
+ async def upload(file: FileData = File()) -> dict:
230
+ pass
231
+
232
+
233
+ await upload(
234
+ FileData(
235
+ content=b"hello",
236
+ filename="hello.txt",
237
+ content_type="text/plain",
238
+ )
239
+ )
240
+ ```
241
+
242
+ Use `AsyncIterator[bytes]` or `AsyncIterator[str]` for raw streams. Other item types are decoded from Server-Sent Events:
243
+
244
+ ```python
245
+ from collections.abc import AsyncIterator
246
+
247
+
248
+ @client.get("/events")
249
+ async def events() -> AsyncIterator[dict]:
250
+ pass
251
+
252
+
253
+ async for event in events():
254
+ print(event)
255
+ ```
256
+
257
+ Streaming requires the httpx or aiohttp Runtime. Retries stop after the first chunk has been delivered, preventing duplicate data.
258
+
259
+ ## Pluggable Runtimes
260
+
261
+ The default `UrllibRuntime` needs no extra HTTP dependency. Existing httpx and aiohttp clients can be injected when you need their connection settings or ecosystem integrations:
262
+
263
+ ```python
264
+ import httpx
265
+
266
+ from typact import HttpClient, HttpxRuntime
267
+
268
+
269
+ client = HttpClient(
270
+ "https://api.example.com",
271
+ client_runtime=HttpxRuntime(httpx.AsyncClient(timeout=30)),
272
+ )
273
+ ```
274
+
275
+ Endpoint declarations and return types stay unchanged when the Runtime changes.
276
+
277
+ ## Testing without a server
278
+
279
+ The Mock Runtime replaces the transport boundary and records every built request:
280
+
281
+ ```python
282
+ from typact import HttpClient, MockRuntime, Query
283
+
284
+
285
+ runtime = MockRuntime()
286
+ runtime.add_response(
287
+ "GET",
288
+ "https://api.example.com/items",
289
+ json_data={"items": []},
290
+ )
291
+ client = HttpClient("https://api.example.com", client_runtime=runtime)
292
+
293
+
294
+ @client.get("/items")
295
+ async def list_items(page: int = Query(1)) -> dict:
296
+ pass
297
+
298
+
299
+ result = await list_items(page=2)
300
+ assert result == {"items": []}
301
+ assert runtime.requests[0].params == {"page": 2}
302
+ ```
303
+
304
+ ## Documentation and examples
305
+
306
+ - [Getting started](https://typact.zzq.jl.cn/docs/getting-started/)
307
+ - [Parameter annotations](https://typact.zzq.jl.cn/docs/annotations/)
308
+ - [Runtimes, retries, streaming, and events](https://typact.zzq.jl.cn/docs/runtime/)
309
+ - [Interceptors](https://typact.zzq.jl.cn/docs/interceptors/)
310
+ - [Testing](https://typact.zzq.jl.cn/docs/testing/)
311
+ - [`examples/`](https://github.com/zzqjlcn/typact/tree/main/examples)
312
+
313
+ ## Development
314
+
315
+ ```bash
316
+ uv sync --all-extras --group dev
317
+ uv run --no-sync pytest -q
318
+ ```
319
+
320
+ Design changes must account for public API clarity, Runtime consistency, backward compatibility, and migration cost. See [CONTRIBUTING.md](https://github.com/zzqjlcn/typact/blob/main/CONTRIBUTING.md) and [RELEASING.md](https://github.com/zzqjlcn/typact/blob/main/RELEASING.md) before contributing or publishing.
321
+
322
+ ## License
323
+
324
+ Typact is released under the [MIT License](https://github.com/zzqjlcn/typact/blob/main/LICENSE).